Merge "[CS] Move LkscrnShadeTransitionCtlr to ShadeInteractor#isShadeEnabled." into udc-qpr-dev
diff --git a/core/java/android/app/TaskInfo.java b/core/java/android/app/TaskInfo.java
index c4e4995..2b5175c 100644
--- a/core/java/android/app/TaskInfo.java
+++ b/core/java/android/app/TaskInfo.java
@@ -242,6 +242,18 @@
     public boolean isLetterboxDoubleTapEnabled;
 
     /**
+     * Whether the user aspect ratio settings button is enabled
+     * @hide
+     */
+    public boolean topActivityEligibleForUserAspectRatioButton;
+
+    /**
+     * Hint about the letterbox state of the top activity.
+     * @hide
+     */
+    public boolean topActivityBoundsLetterboxed;
+
+    /**
      * Whether the update comes from a letterbox double-tap action from the user or not.
      * @hide
      */
@@ -460,7 +472,8 @@
     public boolean hasCompatUI() {
         return hasCameraCompatControl() || topActivityInSizeCompat
                 || topActivityEligibleForLetterboxEducation
-                || isLetterboxDoubleTapEnabled;
+                || isLetterboxDoubleTapEnabled
+                || topActivityEligibleForUserAspectRatioButton;
     }
 
     /**
@@ -510,6 +523,8 @@
                 && supportsMultiWindow == that.supportsMultiWindow
                 && displayAreaFeatureId == that.displayAreaFeatureId
                 && isFromLetterboxDoubleTap == that.isFromLetterboxDoubleTap
+                && topActivityEligibleForUserAspectRatioButton
+                    == that.topActivityEligibleForUserAspectRatioButton
                 && topActivityLetterboxVerticalPosition == that.topActivityLetterboxVerticalPosition
                 && topActivityLetterboxWidth == that.topActivityLetterboxWidth
                 && topActivityLetterboxHeight == that.topActivityLetterboxHeight
@@ -543,6 +558,8 @@
                 && taskId == that.taskId
                 && topActivityInSizeCompat == that.topActivityInSizeCompat
                 && isFromLetterboxDoubleTap == that.isFromLetterboxDoubleTap
+                && topActivityEligibleForUserAspectRatioButton
+                    == that.topActivityEligibleForUserAspectRatioButton
                 && topActivityEligibleForLetterboxEducation
                     == that.topActivityEligibleForLetterboxEducation
                 && topActivityLetterboxVerticalPosition == that.topActivityLetterboxVerticalPosition
@@ -606,6 +623,8 @@
         displayAreaFeatureId = source.readInt();
         cameraCompatControlState = source.readInt();
         isLetterboxDoubleTapEnabled = source.readBoolean();
+        topActivityEligibleForUserAspectRatioButton = source.readBoolean();
+        topActivityBoundsLetterboxed = source.readBoolean();
         isFromLetterboxDoubleTap = source.readBoolean();
         topActivityLetterboxVerticalPosition = source.readInt();
         topActivityLetterboxHorizontalPosition = source.readInt();
@@ -660,6 +679,8 @@
         dest.writeInt(displayAreaFeatureId);
         dest.writeInt(cameraCompatControlState);
         dest.writeBoolean(isLetterboxDoubleTapEnabled);
+        dest.writeBoolean(topActivityEligibleForUserAspectRatioButton);
+        dest.writeBoolean(topActivityBoundsLetterboxed);
         dest.writeBoolean(isFromLetterboxDoubleTap);
         dest.writeInt(topActivityLetterboxVerticalPosition);
         dest.writeInt(topActivityLetterboxHorizontalPosition);
@@ -701,8 +722,11 @@
                 + " topActivityInSizeCompat=" + topActivityInSizeCompat
                 + " topActivityEligibleForLetterboxEducation= "
                         + topActivityEligibleForLetterboxEducation
-                + " topActivityLetterboxed= " + isLetterboxDoubleTapEnabled
-                + " isFromDoubleTap= " + isFromLetterboxDoubleTap
+                + " isLetterboxDoubleTapEnabled= " + isLetterboxDoubleTapEnabled
+                + " topActivityEligibleForUserAspectRatioButton= "
+                + topActivityEligibleForUserAspectRatioButton
+                + " topActivityBoundsLetterboxed= " + topActivityBoundsLetterboxed
+                + " isFromLetterboxDoubleTap= " + isFromLetterboxDoubleTap
                 + " topActivityLetterboxVerticalPosition= " + topActivityLetterboxVerticalPosition
                 + " topActivityLetterboxHorizontalPosition= "
                         + topActivityLetterboxHorizontalPosition
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index 06635ee..6d82922 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -683,7 +683,7 @@
     public static final int BIND_EXTERNAL_SERVICE = 0x80000000;
 
     /**
-     * Works in the same way as {@link #BIND_EXTERNAL_SERVICE}, but it's defined as a (@code long)
+     * Works in the same way as {@link #BIND_EXTERNAL_SERVICE}, but it's defined as a {@code long}
      * value that is compatible to {@link BindServiceFlags}.
      */
     public static final long BIND_EXTERNAL_SERVICE_LONG = 1L << 62;
@@ -1413,7 +1413,7 @@
      * </ul>
      * <p>
      * If a shared storage device is emulated (as determined by
-     * {@link Environment#isExternalStorageEmulated(File)}), it's contents are
+     * {@link Environment#isExternalStorageEmulated(File)}), its contents are
      * backed by a private user data partition, which means there is little
      * benefit to storing data here instead of the private directories returned
      * by {@link #getFilesDir()}, etc.
@@ -1501,7 +1501,7 @@
      * </ul>
      * <p>
      * If a shared storage device is emulated (as determined by
-     * {@link Environment#isExternalStorageEmulated(File)}), it's contents are
+     * {@link Environment#isExternalStorageEmulated(File)}), its contents are
      * backed by a private user data partition, which means there is little
      * benefit to storing data here instead of the private directories returned
      * by {@link #getFilesDir()}, etc.
@@ -1812,7 +1812,7 @@
      * </ul>
      * <p>
      * If a shared storage device is emulated (as determined by
-     * {@link Environment#isExternalStorageEmulated(File)}), it's contents are
+     * {@link Environment#isExternalStorageEmulated(File)}), its contents are
      * backed by a private user data partition, which means there is little
      * benefit to storing data here instead of the private directory returned by
      * {@link #getCacheDir()}.
diff --git a/core/java/android/hardware/fingerprint/FingerprintManager.java b/core/java/android/hardware/fingerprint/FingerprintManager.java
index 6195443..f594377 100644
--- a/core/java/android/hardware/fingerprint/FingerprintManager.java
+++ b/core/java/android/hardware/fingerprint/FingerprintManager.java
@@ -980,23 +980,6 @@
     }
 
     /**
-     * @hide
-     */
-    @RequiresPermission(USE_BIOMETRIC_INTERNAL)
-    public void setUdfpsOverlay(@NonNull IUdfpsOverlay controller) {
-        if (mService == null) {
-            Slog.w(TAG, "setUdfpsOverlay: no fingerprint service");
-            return;
-        }
-
-        try {
-            mService.setUdfpsOverlay(controller);
-        } catch (RemoteException e) {
-            throw e.rethrowFromSystemServer();
-        }
-    }
-
-    /**
      * Forwards BiometricStateListener to FingerprintService
      * @param listener new BiometricStateListener being added
      * @hide
diff --git a/core/java/android/hardware/fingerprint/IFingerprintService.aidl b/core/java/android/hardware/fingerprint/IFingerprintService.aidl
index ff2f313..9975852 100644
--- a/core/java/android/hardware/fingerprint/IFingerprintService.aidl
+++ b/core/java/android/hardware/fingerprint/IFingerprintService.aidl
@@ -27,7 +27,6 @@
 import android.hardware.fingerprint.IFingerprintServiceReceiver;
 import android.hardware.fingerprint.IUdfpsOverlayController;
 import android.hardware.fingerprint.ISidefpsController;
-import android.hardware.fingerprint.IUdfpsOverlay;
 import android.hardware.fingerprint.Fingerprint;
 import android.hardware.fingerprint.FingerprintAuthenticateOptions;
 import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
@@ -203,10 +202,6 @@
     @EnforcePermission("USE_BIOMETRIC_INTERNAL")
     void setSidefpsController(in ISidefpsController controller);
 
-    // Sets the controller for managing the UDFPS overlay.
-    @EnforcePermission("USE_BIOMETRIC_INTERNAL")
-    void setUdfpsOverlay(in IUdfpsOverlay controller);
-
     // Registers BiometricStateListener.
     @EnforcePermission("USE_BIOMETRIC_INTERNAL")
     void registerBiometricStateListener(IBiometricStateListener listener);
diff --git a/core/java/android/hardware/fingerprint/IUdfpsOverlay.aidl b/core/java/android/hardware/fingerprint/IUdfpsOverlay.aidl
deleted file mode 100644
index c99fccc..0000000
--- a/core/java/android/hardware/fingerprint/IUdfpsOverlay.aidl
+++ /dev/null
@@ -1,29 +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 android.hardware.fingerprint;
-
-/**
- * Interface for interacting with the under-display fingerprint sensor (UDFPS) overlay.
- * @hide
- */
-oneway interface IUdfpsOverlay {
-    // Shows the overlay.
-    void show(long requestId, int sensorId, int reason);
-
-    // Hides the overlay.
-    void hide(int sensorId);
-}
diff --git a/core/java/android/inputmethodservice/InputMethodService.java b/core/java/android/inputmethodservice/InputMethodService.java
index 2f9c207..a9c4818 100644
--- a/core/java/android/inputmethodservice/InputMethodService.java
+++ b/core/java/android/inputmethodservice/InputMethodService.java
@@ -52,6 +52,8 @@
 import static android.view.WindowInsets.Type.navigationBars;
 import static android.view.WindowInsets.Type.statusBars;
 
+import static java.lang.annotation.RetentionPolicy.SOURCE;
+
 import android.annotation.AnyThread;
 import android.annotation.CallSuper;
 import android.annotation.DrawableRes;
@@ -158,7 +160,6 @@
 import com.android.internal.inputmethod.InputMethodPrivilegedOperations;
 import com.android.internal.inputmethod.InputMethodPrivilegedOperationsRegistry;
 import com.android.internal.inputmethod.SoftInputShowHideReason;
-import com.android.internal.util.Preconditions;
 import com.android.internal.util.RingBuffer;
 
 import org.xmlpull.v1.XmlPullParserException;
@@ -481,53 +482,43 @@
     public static final int BACK_DISPOSITION_ADJUST_NOTHING = 3;
 
     /**
-     * Enum values to be used for {@link #setBackDisposition(int)}.
+     * Enum flag to be used for {@link #setBackDisposition(int)}.
      *
      * @hide
      */
-    @IntDef(prefix = { "BACK_DISPOSITION_" }, value = {
-            BACK_DISPOSITION_DEFAULT,
-            BACK_DISPOSITION_WILL_NOT_DISMISS,
-            BACK_DISPOSITION_WILL_DISMISS,
-            BACK_DISPOSITION_ADJUST_NOTHING,
-    })
-    @Retention(RetentionPolicy.SOURCE)
+    @Retention(SOURCE)
+    @IntDef(value = {BACK_DISPOSITION_DEFAULT, BACK_DISPOSITION_WILL_NOT_DISMISS,
+            BACK_DISPOSITION_WILL_DISMISS, BACK_DISPOSITION_ADJUST_NOTHING},
+            prefix = "BACK_DISPOSITION_")
     public @interface BackDispositionMode {}
 
     /**
-     * Enum flags to be used for {@link #setImeWindowStatus}, representing the current state of the
-     * IME window visibility.
-     *
      * @hide
-     */
-    @IntDef(flag = true, prefix = { "IME_" }, value = {
-            IME_ACTIVE,
-            IME_VISIBLE,
-            IME_VISIBLE_IMPERCEPTIBLE,
-    })
-    @Retention(RetentionPolicy.SOURCE)
-    public @interface ImeWindowVisibility {}
-
-    /**
      * The IME is active.  It may or may not be visible.
-     * @hide
      */
     public static final int IME_ACTIVE = 0x1;
 
     /**
-     * The IME is perceptibly visible to the user.
      * @hide
+     * The IME is perceptibly visible to the user.
      */
     public static final int IME_VISIBLE = 0x2;
 
     /**
+     * @hide
+     * The IME is active and ready with views but set invisible.
+     * This flag cannot be combined with {@link #IME_VISIBLE}.
+     */
+    public static final int IME_INVISIBLE = 0x4;
+
+    /**
+     * @hide
      * The IME is visible, but not yet perceptible to the user (e.g. fading in)
      * by {@link android.view.WindowInsetsController}.
      *
      * @see InputMethodManager#reportPerceptible
-     * @hide
      */
-    public static final int IME_VISIBLE_IMPERCEPTIBLE = 0x4;
+    public static final int IME_VISIBLE_IMPERCEPTIBLE = 0x8;
 
     // Min and max values for back disposition.
     private static final int BACK_DISPOSITION_MIN = BACK_DISPOSITION_DEFAULT;
@@ -640,18 +631,9 @@
     
     int mStatusIcon;
 
-    /**
-     * Latest value reported of back disposition mode.
-     */
     @BackDispositionMode
     int mBackDisposition;
 
-    /**
-     * Latest value reported of IME window visibility flags.
-     */
-    @ImeWindowVisibility
-    private int mImeWindowVisibility;
-
     private Object mLock = new Object();
     @GuardedBy("mLock")
     private boolean mNotifyUserActionSent;
@@ -1228,14 +1210,8 @@
         mImeSurfaceRemoverRunnable = null;
     }
 
-    private void setImeWindowStatus(@ImeWindowVisibility int vis,
-            @BackDispositionMode int backDisposition) {
-        if (vis == mImeWindowVisibility && backDisposition == mBackDisposition) {
-            return;
-        }
-        mImeWindowVisibility = Preconditions.checkFlagsArgument(vis, IME_ACTIVE | IME_VISIBLE);
-        mBackDisposition = backDisposition;
-        mPrivOps.setImeWindowStatusAsync(mImeWindowVisibility, mBackDisposition);
+    private void setImeWindowStatus(int visibilityFlags, int backDisposition) {
+        mPrivOps.setImeWindowStatusAsync(visibilityFlags, backDisposition);
     }
 
     /** Set region of the keyboard to be avoided from back gesture */
@@ -1909,11 +1885,15 @@
      * @param disposition disposition mode to be set
      */
     public void setBackDisposition(@BackDispositionMode int disposition) {
-        if (disposition < BACK_DISPOSITION_MIN || disposition > BACK_DISPOSITION_MAX) {
+        if (disposition == mBackDisposition) {
+            return;
+        }
+        if (disposition > BACK_DISPOSITION_MAX || disposition < BACK_DISPOSITION_MIN) {
             Log.e(TAG, "Invalid back disposition value (" + disposition + ") specified.");
             return;
         }
-        setImeWindowStatus(mImeWindowVisibility, disposition);
+        mBackDisposition = disposition;
+        setImeWindowStatus(mapToImeWindowStatus(), mBackDisposition);
     }
 
     /**
@@ -2887,8 +2867,14 @@
         Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "IMS.showWindow");
         mDecorViewWasVisible = mDecorViewVisible;
         mInShowWindow = true;
+        final int previousImeWindowStatus =
+                (mDecorViewVisible ? IME_ACTIVE : 0) | (isInputViewShown()
+                        ? (!mWindowVisible ? IME_INVISIBLE : IME_VISIBLE) : 0);
         startViews(prepareWindow(showInput));
-        setImeWindowStatus(mapToImeWindowStatus(), mBackDisposition);
+        final int nextImeWindowStatus = mapToImeWindowStatus();
+        if (previousImeWindowStatus != nextImeWindowStatus) {
+            setImeWindowStatus(nextImeWindowStatus, mBackDisposition);
+        }
 
         mNavigationBarController.onWindowShown();
         // compute visibility
@@ -4099,9 +4085,9 @@
         };
     }
 
-    @ImeWindowVisibility
     private int mapToImeWindowStatus() {
-        return IME_ACTIVE | (mDecorViewVisible ? IME_VISIBLE : 0);
+        return IME_ACTIVE
+                | (isInputViewShown() ? IME_VISIBLE : 0);
     }
 
     private boolean isAutomotive() {
diff --git a/core/java/android/os/GraphicsEnvironment.java b/core/java/android/os/GraphicsEnvironment.java
index c9073fa..e6bdfe1 100644
--- a/core/java/android/os/GraphicsEnvironment.java
+++ b/core/java/android/os/GraphicsEnvironment.java
@@ -116,6 +116,8 @@
     private static final String ANGLE_GL_DRIVER_CHOICE_ANGLE = "angle";
     private static final String ANGLE_GL_DRIVER_CHOICE_NATIVE = "native";
 
+    private static final String PROPERTY_RO_ANGLE_SUPPORTED = "ro.gfx.angle.supported";
+
     private ClassLoader mClassLoader;
     private String mLibrarySearchPaths;
     private String mLibraryPermittedPaths;
@@ -501,10 +503,12 @@
         final List<ResolveInfo> resolveInfos =
                 pm.queryIntentActivities(intent, PackageManager.MATCH_SYSTEM_ONLY);
         if (resolveInfos.size() != 1) {
-            Log.e(TAG, "Invalid number of ANGLE packages. Required: 1, Found: "
+            Log.v(TAG, "Invalid number of ANGLE packages. Required: 1, Found: "
                     + resolveInfos.size());
-            for (ResolveInfo resolveInfo : resolveInfos) {
-                Log.e(TAG, "Found ANGLE package: " + resolveInfo.activityInfo.packageName);
+            if (DEBUG) {
+                for (ResolveInfo resolveInfo : resolveInfos) {
+                    Log.d(TAG, "Found ANGLE package: " + resolveInfo.activityInfo.packageName);
+                }
             }
             return "";
         }
@@ -539,26 +543,42 @@
     }
 
     /**
-     * Determine whether ANGLE should be used, set it up if so, and pass ANGLE details down to
-     * the C++ GraphicsEnv class.
+     * Determine whether ANGLE should be used, attempt to set up from apk first, if ANGLE can be
+     * set up from apk, pass ANGLE details down to the C++ GraphicsEnv class via
+     * GraphicsEnv::setAngleInfo(). If apk setup fails, attempt to set up to use system ANGLE.
+     * Return false if both fail.
      *
-     * If ANGLE will be used, GraphicsEnv::setAngleInfo() will be called to enable ANGLE to be
-     * properly used.
-     *
-     * @param context
-     * @param bundle
-     * @param pm
+     * @param context - Context of the application.
+     * @param bundle - Bundle of the application.
+     * @param packageManager - PackageManager of the application process.
      * @param packageName - package name of the application.
-     * @return true: ANGLE setup successfully
-     *         false: ANGLE not setup (not on allowlist, ANGLE not present, etc.)
+     * @return true: can set up to use ANGLE successfully.
+     *         false: can not set up to use ANGLE (not on allowlist, ANGLE not present, etc.)
      */
-    private boolean setupAngle(Context context, Bundle bundle, PackageManager pm,
+    private boolean setupAngle(Context context, Bundle bundle, PackageManager packageManager,
             String packageName) {
 
         if (!shouldUseAngle(context, bundle, packageName)) {
             return false;
         }
 
+        return setupAngleFromApk(context, bundle, packageManager, packageName)
+                || setupAngleFromSystem(context, bundle, packageName);
+    }
+
+    /**
+     * Attempt to set up ANGLE from the packaged apk, if the apk can be found, pass ANGLE details to
+     * the C++ GraphicsEnv class.
+     *
+     * @param context - Context of the application.
+     * @param bundle - Bundle of the application.
+     * @param packageManager - PackageManager of the application process.
+     * @param packageName - package name of the application.
+     * @return true: can set up to use ANGLE apk.
+     *         false: can not set up to use ANGLE apk (ANGLE apk not present, etc.)
+     */
+    private boolean setupAngleFromApk(Context context, Bundle bundle, PackageManager packageManager,
+            String packageName) {
         ApplicationInfo angleInfo = null;
 
         // If the developer has specified a debug package over ADB, attempt to find it
@@ -567,7 +587,7 @@
             Log.v(TAG, "ANGLE debug package enabled: " + anglePkgName);
             try {
                 // Note the debug package does not have to be pre-installed
-                angleInfo = pm.getApplicationInfo(anglePkgName, 0);
+                angleInfo = packageManager.getApplicationInfo(anglePkgName, 0);
             } catch (PackageManager.NameNotFoundException e) {
                 // If the debug package is specified but not found, abort.
                 Log.v(TAG, "ANGLE debug package '" + anglePkgName + "' not installed");
@@ -577,7 +597,7 @@
 
         // Otherwise, check to see if ANGLE is properly installed
         if (angleInfo == null) {
-            anglePkgName = getAnglePackageName(pm);
+            anglePkgName = getAnglePackageName(packageManager);
             if (TextUtils.isEmpty(anglePkgName)) {
                 Log.v(TAG, "Failed to find ANGLE package.");
                 return false;
@@ -586,7 +606,7 @@
             Log.v(TAG, "ANGLE package enabled: " + anglePkgName);
             try {
                 // Production ANGLE libraries must be pre-installed as a system app
-                angleInfo = pm.getApplicationInfo(anglePkgName,
+                angleInfo = packageManager.getApplicationInfo(anglePkgName,
                         PackageManager.MATCH_SYSTEM_ONLY);
             } catch (PackageManager.NameNotFoundException e) {
                 Log.v(TAG, "ANGLE package '" + anglePkgName + "' not installed");
@@ -610,12 +630,36 @@
         // If we make it to here, ANGLE will be used.  Call setAngleInfo() with the package name,
         // and features to use.
         final String[] features = getAngleEglFeatures(context, bundle);
-        setAngleInfo(paths, packageName, ANGLE_GL_DRIVER_CHOICE_ANGLE, features);
+        setAngleInfo(paths, false, packageName, features);
 
         return true;
     }
 
     /**
+     * Attempt to set up ANGLE from system, if the apk can be found, pass ANGLE details to
+     * the C++ GraphicsEnv class.
+     *
+     * @param context - Context of the application.
+     * @param bundle - Bundle of the application.
+     * @param packageName - package name of the application.
+     * @return true: can set up to use system ANGLE.
+     *         false: can not set up to use system ANGLE because it doesn't exist.
+     */
+    private boolean setupAngleFromSystem(Context context, Bundle bundle, String packageName) {
+        final boolean systemAngleSupported = SystemProperties
+                                             .getBoolean(PROPERTY_RO_ANGLE_SUPPORTED, false);
+        if (!systemAngleSupported) {
+            return false;
+        }
+
+        // If we make it to here, ANGLE will be used.  Call setAngleInfo() with the package name,
+        // and features to use.
+        final String[] features = getAngleEglFeatures(context, bundle);
+        setAngleInfo("", true, packageName, features);
+        return true;
+    }
+
+    /**
      * Determine if the "ANGLE In Use" dialog box should be shown.
      */
     private boolean shouldShowAngleInUseDialogBox(Context context) {
@@ -651,7 +695,9 @@
 
         final Intent intent = new Intent(ACTION_ANGLE_FOR_ANDROID_TOAST_MESSAGE);
         final String anglePkg = getAnglePackageName(context.getPackageManager());
-        intent.setPackage(anglePkg);
+        if (anglePkg.isEmpty()) {
+            return;
+        }
 
         context.sendOrderedBroadcast(intent, null, new BroadcastReceiver() {
             @Override
@@ -890,8 +936,8 @@
     private static native void setDriverPathAndSphalLibraries(String path, String sphalLibraries);
     private static native void setGpuStats(String driverPackageName, String driverVersionName,
             long driverVersionCode, long driverBuildTime, String appPackageName, int vulkanVersion);
-    private static native void setAngleInfo(String path, String packageName,
-            String devOptIn, String[] features);
+    private static native void setAngleInfo(String path, boolean useSystemAngle, String packageName,
+            String[] features);
     private static native boolean setInjectLayersPrSetDumpable();
     private static native void nativeToggleAngleAsSystemDriver(boolean enabled);
 
diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java
index 40b060a..3f308e6 100644
--- a/core/java/android/view/inputmethod/InputMethodManager.java
+++ b/core/java/android/view/inputmethod/InputMethodManager.java
@@ -2657,17 +2657,6 @@
             }
         }
 
-        if (windowGainingFocus == null) {
-            windowGainingFocus = view.getWindowToken();
-            if (windowGainingFocus == null) {
-                Log.e(TAG, "ABORT input: ServedView must be attached to a Window");
-                return false;
-            }
-            startInputFlags = getStartInputFlags(view, startInputFlags);
-            softInputMode = view.getViewRootImpl().mWindowAttributes.softInputMode;
-            windowFlags = view.getViewRootImpl().mWindowAttributes.flags;
-        }
-
         // Now we need to get an input connection from the served view.
         // This is complicated in a couple ways: we can't be holding our lock
         // when calling out to the view, and we need to make sure we call into
@@ -2690,6 +2679,17 @@
             return false;
         }
 
+        if (windowGainingFocus == null) {
+            windowGainingFocus = view.getWindowToken();
+            if (windowGainingFocus == null) {
+                Log.e(TAG, "ABORT input: ServedView must be attached to a Window");
+                return false;
+            }
+            startInputFlags = getStartInputFlags(view, startInputFlags);
+            softInputMode = view.getViewRootImpl().mWindowAttributes.softInputMode;
+            windowFlags = view.getViewRootImpl().mWindowAttributes.flags;
+        }
+
         // Okay we are now ready to call into the served view and have it
         // do its stuff.
         // Life is good: let's hook everything up!
diff --git a/core/java/android/widget/Editor.java b/core/java/android/widget/Editor.java
index d37c37a..3da9e96 100644
--- a/core/java/android/widget/Editor.java
+++ b/core/java/android/widget/Editor.java
@@ -8105,6 +8105,16 @@
         private final Paint mHighlightPaint;
         private final Path mHighlightPath;
 
+        /**
+         * Whether it is in the progress of updating transformation method. It's needed because
+         * {@link TextView#setTransformationMethod(TransformationMethod)} will eventually call
+         * {@link TextView#setText(CharSequence)}.
+         * Because it normally should exit insert mode when {@link TextView#setText(CharSequence)}
+         * is called externally, we need this boolean to distinguish whether setText is triggered
+         * by setTransformation or not.
+         */
+        private boolean mUpdatingTransformationMethod;
+
         InsertModeController(@NonNull TextView textView) {
             mTextView = Objects.requireNonNull(textView);
             mIsInsertModeActive = false;
@@ -8137,7 +8147,7 @@
             final boolean isSingleLine = mTextView.isSingleLine();
             mInsertModeTransformationMethod = new InsertModeTransformationMethod(offset,
                     isSingleLine, oldTransformationMethod);
-            mTextView.setTransformationMethodInternal(mInsertModeTransformationMethod);
+            setTransformationMethod(mInsertModeTransformationMethod, true);
             Selection.setSelection((Spannable) mTextView.getText(), offset);
 
             mIsInsertModeActive = true;
@@ -8145,6 +8155,10 @@
         }
 
         void exitInsertMode() {
+            exitInsertMode(true);
+        }
+
+        void exitInsertMode(boolean updateText) {
             if (!mIsInsertModeActive) return;
             if (mInsertModeTransformationMethod == null
                     || mInsertModeTransformationMethod != mTextView.getTransformationMethod()) {
@@ -8157,7 +8171,7 @@
             final int selectionEnd = mTextView.getSelectionEnd();
             final TransformationMethod oldTransformationMethod =
                     mInsertModeTransformationMethod.getOldTransformationMethod();
-            mTextView.setTransformationMethodInternal(oldTransformationMethod);
+            setTransformationMethod(oldTransformationMethod, updateText);
             Selection.setSelection((Spannable) mTextView.getText(), selectionStart, selectionEnd);
             mIsInsertModeActive = false;
         }
@@ -8178,6 +8192,32 @@
         }
 
         /**
+         * Update the TransformationMethod on the {@link TextView}.
+         * @param method the new method to be set on the {@link TextView}/
+         * @param updateText whether to update the text during setTransformationMethod call.
+         */
+        private void setTransformationMethod(TransformationMethod method, boolean updateText) {
+            mUpdatingTransformationMethod = true;
+            mTextView.setTransformationMethodInternal(method, updateText);
+            mUpdatingTransformationMethod = false;
+        }
+
+        /**
+         * Notify the InsertMode controller that the {@link TextView} is about to set its text.
+         */
+        void beforeSetText() {
+            // TextView#setText is called because our call to
+            // TextView#setTransformationMethodInternal in enterInsertMode() or exitInsertMode().
+            // Do nothing in this case.
+            if (mUpdatingTransformationMethod) {
+                return;
+            }
+            // TextView#setText is called externally. Exit InsertMode but don't update text again
+            // when calling setTransformationMethod.
+            exitInsertMode(/* updateText */ false);
+        }
+
+        /**
          * Notify the {@link InsertModeController} before the TextView's
          * {@link TransformationMethod} is updated. If it's not in the insert mode,
          * the given method is directly returned. Otherwise, it will wrap the given transformation
@@ -8205,6 +8245,9 @@
         return mInsertModeController.enterInsertMode(offset);
     }
 
+    /**
+     * Exit insert mode if this editor is in insert mode.
+     */
     void exitInsertMode() {
         if (mInsertModeController == null) return;
         mInsertModeController.exitInsertMode();
@@ -8217,7 +8260,7 @@
      */
     void setTransformationMethod(TransformationMethod method) {
         if (mInsertModeController == null || !mInsertModeController.mIsInsertModeActive) {
-            mTextView.setTransformationMethodInternal(method);
+            mTextView.setTransformationMethodInternal(method, /* updateText */ true);
             return;
         }
 
@@ -8226,11 +8269,19 @@
         final int selectionStart = mTextView.getSelectionStart();
         final int selectionEnd = mTextView.getSelectionEnd();
         method = mInsertModeController.updateTransformationMethod(method);
-        mTextView.setTransformationMethodInternal(method);
+        mTextView.setTransformationMethodInternal(method, /* updateText */ true);
         Selection.setSelection((Spannable) mTextView.getText(), selectionStart, selectionEnd);
     }
 
     /**
+     * Notify that the Editor that the associated {@link TextView} is about to set its text.
+     */
+    void beforeSetText() {
+        if (mInsertModeController == null) return;
+        mInsertModeController.beforeSetText();
+    }
+
+    /**
      * Initializes the nodeInfo with smart actions.
      */
     void onInitializeSmartActionsAccessibilityNodeInfo(AccessibilityNodeInfo nodeInfo) {
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index 7e1e52d..438b974 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -2795,11 +2795,22 @@
         if (mEditor != null) {
             mEditor.setTransformationMethod(method);
         } else {
-            setTransformationMethodInternal(method);
+            setTransformationMethodInternal(method, /* updateText */ true);
         }
     }
 
-    void setTransformationMethodInternal(@Nullable TransformationMethod method) {
+    /**
+     * Set the transformation that is applied to the text that this TextView is displaying,
+     * optionally call the setText.
+     * @param method the new transformation method to be set.
+     * @param updateText whether the call {@link #setText} which will update the TextView to display
+     *                   the new content. This method is helpful when updating
+     *                   {@link TransformationMethod} inside {@link #setText}. It should only be
+     *                   false if text will be updated immediately after this call, otherwise the
+     *                   TextView will enter an inconsistent state.
+     */
+    void setTransformationMethodInternal(@Nullable TransformationMethod method,
+            boolean updateText) {
         if (method == mTransformation) {
             // Avoid the setText() below if the transformation is
             // the same.
@@ -2821,7 +2832,9 @@
             mAllowTransformationLengthChange = false;
         }
 
-        setText(mText);
+        if (updateText) {
+            setText(mText);
+        }
 
         if (hasPasswordTransformationMethod()) {
             notifyViewAccessibilityStateChangedIfNeeded(
@@ -7000,6 +7013,9 @@
     @UnsupportedAppUsage
     private void setText(CharSequence text, BufferType type,
                          boolean notifyBefore, int oldlen) {
+        if (mEditor != null) {
+            mEditor.beforeSetText();
+        }
         mTextSetFromXmlOrResourceId = false;
         if (text == null) {
             text = "";
@@ -13811,13 +13827,14 @@
     }
 
     /**
-     * Helper method to set {@code rect} to the text content's non-clipped area in the view's
-     * coordinates.
+     * Helper method to set {@code rect} to this TextView's non-clipped area in its own coordinates.
+     * This method obtains the view's visible rectangle whereas the method
+     * {@link #getContentVisibleRect} returns the text layout's visible rectangle.
      *
      * @return true if at least part of the text content is visible; false if the text content is
      * completely clipped or translated out of the visible area.
      */
-    private boolean getContentVisibleRect(Rect rect) {
+    private boolean getViewVisibleRect(Rect rect) {
         if (!getLocalVisibleRect(rect)) {
             return false;
         }
@@ -13826,6 +13843,20 @@
         // view's coordinates. So we need to offset it with the negative scrolled amount to convert
         // it to view's coordinate.
         rect.offset(-getScrollX(), -getScrollY());
+        return true;
+    }
+
+    /**
+     * Helper method to set {@code rect} to the text content's non-clipped area in the view's
+     * coordinates.
+     *
+     * @return true if at least part of the text content is visible; false if the text content is
+     * completely clipped or translated out of the visible area.
+     */
+    private boolean getContentVisibleRect(Rect rect) {
+        if (!getViewVisibleRect(rect)) {
+            return false;
+        }
         // Clip the view's visible rect with the text layout's visible rect.
         return rect.intersect(getCompoundPaddingLeft(), getCompoundPaddingTop(),
                 getWidth() - getCompoundPaddingRight(), getHeight() - getCompoundPaddingBottom());
@@ -13955,14 +13986,25 @@
         builder.setMatrix(viewToScreenMatrix);
 
         if (includeEditorBounds) {
-            final RectF editorBounds = new RectF();
-            editorBounds.set(0 /* left */, 0 /* top */,
-                    getWidth(), getHeight());
-            final RectF handwritingBounds = new RectF(
-                    -getHandwritingBoundsOffsetLeft(),
-                    -getHandwritingBoundsOffsetTop(),
-                    getWidth() + getHandwritingBoundsOffsetRight(),
-                    getHeight() + getHandwritingBoundsOffsetBottom());
+            if (mTempRect == null) {
+                mTempRect = new Rect();
+            }
+            final Rect bounds = mTempRect;
+            final RectF editorBounds;
+            final RectF handwritingBounds;
+            if (getViewVisibleRect(bounds)) {
+                editorBounds = new RectF(bounds);
+                handwritingBounds = new RectF(editorBounds);
+                handwritingBounds.top -= getHandwritingBoundsOffsetTop();
+                handwritingBounds.left -= getHandwritingBoundsOffsetLeft();
+                handwritingBounds.bottom += getHandwritingBoundsOffsetBottom();
+                handwritingBounds.right += getHandwritingBoundsOffsetRight();
+            } else {
+                // The editor is not visible at all, return empty rectangles. We still need to
+                // return an EditorBoundsInfo because IME has subscribed the EditorBoundsInfo.
+                editorBounds = new RectF();
+                handwritingBounds = new RectF();
+            }
             EditorBoundsInfo.Builder boundsBuilder = new EditorBoundsInfo.Builder();
             EditorBoundsInfo editorBoundsInfo = boundsBuilder.setEditorBounds(editorBounds)
                     .setHandwritingBounds(handwritingBounds).build();
diff --git a/core/java/com/android/internal/inputmethod/InputMethodPrivilegedOperations.java b/core/java/com/android/internal/inputmethod/InputMethodPrivilegedOperations.java
index 1a38049..66e3333 100644
--- a/core/java/com/android/internal/inputmethod/InputMethodPrivilegedOperations.java
+++ b/core/java/com/android/internal/inputmethod/InputMethodPrivilegedOperations.java
@@ -20,7 +20,6 @@
 import android.annotation.DrawableRes;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
-import android.inputmethodservice.InputMethodService;
 import android.net.Uri;
 import android.os.IBinder;
 import android.os.RemoteException;
@@ -106,10 +105,14 @@
      *
      * @param vis visibility flags
      * @param backDisposition disposition flags
+     * @see android.inputmethodservice.InputMethodService#IME_ACTIVE
+     * @see android.inputmethodservice.InputMethodService#IME_VISIBLE
+     * @see android.inputmethodservice.InputMethodService#IME_INVISIBLE
+     * @see android.inputmethodservice.InputMethodService#BACK_DISPOSITION_DEFAULT
+     * @see android.inputmethodservice.InputMethodService#BACK_DISPOSITION_ADJUST_NOTHING
      */
     @AnyThread
-    public void setImeWindowStatusAsync(@InputMethodService.ImeWindowVisibility int vis,
-            @InputMethodService.BackDispositionMode int backDisposition) {
+    public void setImeWindowStatusAsync(int vis, int backDisposition) {
         final IInputMethodPrivilegedOperations ops = mOps.getAndWarnIfNull();
         if (ops == null) {
             return;
diff --git a/core/java/com/android/internal/statusbar/RegisterStatusBarResult.java b/core/java/com/android/internal/statusbar/RegisterStatusBarResult.java
index 8b9a991..4f827cd 100644
--- a/core/java/com/android/internal/statusbar/RegisterStatusBarResult.java
+++ b/core/java/com/android/internal/statusbar/RegisterStatusBarResult.java
@@ -16,7 +16,6 @@
 
 package com.android.internal.statusbar;
 
-import android.inputmethodservice.InputMethodService;
 import android.os.IBinder;
 import android.os.Parcel;
 import android.os.Parcelable;
@@ -32,9 +31,7 @@
     public final int mDisabledFlags1;                   // switch[0]
     public final int mAppearance;                       // switch[1]
     public final AppearanceRegion[] mAppearanceRegions; // switch[2]
-    @InputMethodService.ImeWindowVisibility
     public final int mImeWindowVis;                     // switch[3]
-    @InputMethodService.BackDispositionMode
     public final int mImeBackDisposition;               // switch[4]
     public final boolean mShowImeSwitcher;              // switch[5]
     public final int mDisabledFlags2;                   // switch[6]
@@ -47,12 +44,10 @@
     public final LetterboxDetails[] mLetterboxDetails;
 
     public RegisterStatusBarResult(ArrayMap<String, StatusBarIcon> icons, int disabledFlags1,
-            int appearance, AppearanceRegion[] appearanceRegions,
-            @InputMethodService.ImeWindowVisibility int imeWindowVis,
-            @InputMethodService.BackDispositionMode int imeBackDisposition, boolean showImeSwitcher,
-            int disabledFlags2, IBinder imeToken, boolean navbarColorManagedByIme, int behavior,
-            int requestedVisibleTypes, String packageName, int transientBarTypes,
-            LetterboxDetails[] letterboxDetails) {
+            int appearance, AppearanceRegion[] appearanceRegions, int imeWindowVis,
+            int imeBackDisposition, boolean showImeSwitcher, int disabledFlags2, IBinder imeToken,
+            boolean navbarColorManagedByIme, int behavior, int requestedVisibleTypes,
+            String packageName, int transientBarTypes, LetterboxDetails[] letterboxDetails) {
         mIcons = new ArrayMap<>(icons);
         mDisabledFlags1 = disabledFlags1;
         mAppearance = appearance;
diff --git a/core/java/com/android/internal/widget/NotificationExpandButton.java b/core/java/com/android/internal/widget/NotificationExpandButton.java
index 07ee9b5..d4dd1e7 100644
--- a/core/java/com/android/internal/widget/NotificationExpandButton.java
+++ b/core/java/com/android/internal/widget/NotificationExpandButton.java
@@ -21,6 +21,8 @@
 import android.content.Context;
 import android.content.res.ColorStateList;
 import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
+import android.graphics.drawable.LayerDrawable;
 import android.util.AttributeSet;
 import android.view.RemotableViewMethod;
 import android.view.View;
@@ -42,7 +44,7 @@
 @RemoteViews.RemoteView
 public class NotificationExpandButton extends FrameLayout {
 
-    private View mPillView;
+    private Drawable mPillDrawable;
     private TextView mNumberView;
     private ImageView mIconView;
     private boolean mExpanded;
@@ -73,7 +75,10 @@
     @Override
     protected void onFinishInflate() {
         super.onFinishInflate();
-        mPillView = findViewById(R.id.expand_button_pill);
+
+        final View pillView = findViewById(R.id.expand_button_pill);
+        final LayerDrawable layeredPill = (LayerDrawable) pillView.getBackground();
+        mPillDrawable = layeredPill.findDrawableByLayerId(R.id.expand_button_pill_colorized_layer);
         mNumberView = findViewById(R.id.expand_button_number);
         mIconView = findViewById(R.id.expand_button_icon);
     }
@@ -156,7 +161,7 @@
     private void updateColors() {
         if (shouldShowNumber()) {
             if (mHighlightPillColor != 0) {
-                mPillView.setBackgroundTintList(ColorStateList.valueOf(mHighlightPillColor));
+                mPillDrawable.setTintList(ColorStateList.valueOf(mHighlightPillColor));
             }
             mIconView.setColorFilter(mHighlightTextColor);
             if (mHighlightTextColor != 0) {
@@ -164,7 +169,7 @@
             }
         } else {
             if (mDefaultPillColor != 0) {
-                mPillView.setBackgroundTintList(ColorStateList.valueOf(mDefaultPillColor));
+                mPillDrawable.setTintList(ColorStateList.valueOf(mDefaultPillColor));
             }
             mIconView.setColorFilter(mDefaultTextColor);
             if (mDefaultTextColor != 0) {
diff --git a/core/jni/android_os_GraphicsEnvironment.cpp b/core/jni/android_os_GraphicsEnvironment.cpp
index d94b982..afc3cbd 100644
--- a/core/jni/android_os_GraphicsEnvironment.cpp
+++ b/core/jni/android_os_GraphicsEnvironment.cpp
@@ -16,11 +16,12 @@
 
 #define LOG_TAG "GraphicsEnvironment"
 
-#include <vector>
-
 #include <graphicsenv/GraphicsEnv.h>
 #include <nativehelper/ScopedUtfChars.h>
 #include <nativeloader/native_loader.h>
+
+#include <vector>
+
 #include "core_jni_helpers.h"
 
 namespace {
@@ -49,11 +50,10 @@
                                                     appPackageNameChars.c_str(), vulkanVersion);
 }
 
-void setAngleInfo_native(JNIEnv* env, jobject clazz, jstring path, jstring packageName,
-                         jstring devOptIn, jobjectArray featuresObj) {
+void setAngleInfo_native(JNIEnv* env, jobject clazz, jstring path, jboolean useSystemAngle,
+                         jstring packageName, jobjectArray featuresObj) {
     ScopedUtfChars pathChars(env, path);
     ScopedUtfChars packageNameChars(env, packageName);
-    ScopedUtfChars devOptInChars(env, devOptIn);
 
     std::vector<std::string> features;
     if (featuresObj != nullptr) {
@@ -73,8 +73,8 @@
         }
     }
 
-    android::GraphicsEnv::getInstance().setAngleInfo(pathChars.c_str(), packageNameChars.c_str(),
-                                                     devOptInChars.c_str(), features);
+    android::GraphicsEnv::getInstance().setAngleInfo(pathChars.c_str(), useSystemAngle,
+                                                     packageNameChars.c_str(), features);
 }
 
 void setLayerPaths_native(JNIEnv* env, jobject clazz, jobject classLoader, jstring layerPaths) {
@@ -118,8 +118,7 @@
          reinterpret_cast<void*>(setGpuStats_native)},
         {"setInjectLayersPrSetDumpable", "()Z",
          reinterpret_cast<void*>(setInjectLayersPrSetDumpable_native)},
-        {"setAngleInfo",
-         "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;[Ljava/lang/String;)V",
+        {"setAngleInfo", "(Ljava/lang/String;ZLjava/lang/String;[Ljava/lang/String;)V",
          reinterpret_cast<void*>(setAngleInfo_native)},
         {"setLayerPaths", "(Ljava/lang/ClassLoader;Ljava/lang/String;)V",
          reinterpret_cast<void*>(setLayerPaths_native)},
diff --git a/core/res/res/color-night/notification_expand_button_state_tint.xml b/core/res/res/color-night/notification_expand_button_state_tint.xml
new file mode 100644
index 0000000..a794d53
--- /dev/null
+++ b/core/res/res/color-night/notification_expand_button_state_tint.xml
@@ -0,0 +1,21 @@
+<!--
+  ~ 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.
+  -->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:state_pressed="true" android:color="@android:color/system_on_surface_dark" android:alpha="0.06"/>
+    <item android:state_hovered="true" android:color="@android:color/system_on_surface_dark" android:alpha="0.03"/>
+    <item android:color="@android:color/system_on_surface_dark" android:alpha="0.00"/>
+</selector>
\ No newline at end of file
diff --git a/core/res/res/color/notification_expand_button_state_tint.xml b/core/res/res/color/notification_expand_button_state_tint.xml
new file mode 100644
index 0000000..67b2c25
--- /dev/null
+++ b/core/res/res/color/notification_expand_button_state_tint.xml
@@ -0,0 +1,21 @@
+<!--
+  ~ 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.
+  -->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:state_pressed="true" android:color="@android:color/system_on_surface_light" android:alpha="0.12"/>
+    <item android:state_hovered="true" android:color="@android:color/system_on_surface_light" android:alpha="0.08"/>
+    <item android:color="@android:color/system_on_surface_light" android:alpha="0.00"/>
+</selector>
\ No newline at end of file
diff --git a/core/res/res/drawable/expand_button_pill_bg.xml b/core/res/res/drawable/expand_button_pill_bg.xml
index f95044a..a14d33c 100644
--- a/core/res/res/drawable/expand_button_pill_bg.xml
+++ b/core/res/res/drawable/expand_button_pill_bg.xml
@@ -13,7 +13,17 @@
   ~ See the License for the specific language governing permissions and
   ~ limitations under the License.
   -->
-<shape xmlns:android="http://schemas.android.com/apk/res/android">
-    <corners android:radius="@dimen/notification_expand_button_pill_height" />
-    <solid android:color="@android:color/white" />
-</shape>
\ No newline at end of file
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:id="@+id/expand_button_pill_colorized_layer">
+        <shape xmlns:android="http://schemas.android.com/apk/res/android">
+            <corners android:radius="@dimen/notification_expand_button_pill_height" />
+            <solid android:color="@android:color/white" />
+        </shape>
+    </item>
+    <item>
+        <shape xmlns:android="http://schemas.android.com/apk/res/android">
+            <corners android:radius="@dimen/notification_expand_button_pill_height" />
+            <solid android:color="@color/notification_expand_button_state_tint" />
+        </shape>
+    </item>
+</layer-list>
\ No newline at end of file
diff --git a/core/res/res/layout/notification_expand_button.xml b/core/res/res/layout/notification_expand_button.xml
index 8eae064..63fe471 100644
--- a/core/res/res/layout/notification_expand_button.xml
+++ b/core/res/res/layout/notification_expand_button.xml
@@ -34,6 +34,7 @@
         android:background="@drawable/expand_button_pill_bg"
         android:gravity="center_vertical"
         android:layout_gravity="center_vertical"
+        android:duplicateParentState="true"
         >
 
         <TextView
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index f27535c..3be0d7f 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -46,6 +46,7 @@
         <item><xliff:g id="id">@string/status_bar_secure</xliff:g></item>
         <item><xliff:g id="id">@string/status_bar_managed_profile</xliff:g></item>
         <item><xliff:g id="id">@string/status_bar_cast</xliff:g></item>
+        <item><xliff:g id="id">@string/status_bar_connected_display</xliff:g></item>
         <item><xliff:g id="id">@string/status_bar_screen_record</xliff:g></item>
         <item><xliff:g id="id">@string/status_bar_vpn</xliff:g></item>
         <item><xliff:g id="id">@string/status_bar_bluetooth</xliff:g></item>
@@ -72,6 +73,7 @@
     <string translatable="false" name="status_bar_sync_failing">sync_failing</string>
     <string translatable="false" name="status_bar_sync_active">sync_active</string>
     <string translatable="false" name="status_bar_cast">cast</string>
+    <string translatable="false" name="status_bar_connected_display">connected_display</string>
     <string translatable="false" name="status_bar_hotspot">hotspot</string>
     <string translatable="false" name="status_bar_location">location</string>
     <string translatable="false" name="status_bar_bluetooth">bluetooth</string>
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index 04fef58..f795bd7 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -3792,7 +3792,7 @@
          keyboard is connected -->
     <string name="show_ime">Keep it on screen while physical keyboard is active</string>
     <!-- Title of the physical keyboard category in the input method selector [CHAR LIMIT=30] -->
-    <string name="hardware">Show virtual keyboard</string>
+    <string name="hardware">Use on-screen keyboard</string>
 
     <!-- Title of the notification to prompt the user to configure physical keyboard settings. [CHAR LIMIT=NOTIF_TITLE] -->
     <string name="select_keyboard_layout_notification_title">Configure <xliff:g id="device_name" example="Foobar USB Keyboard">%s</xliff:g></string>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 2e5da33..85e9792 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -3050,6 +3050,7 @@
   <java-symbol type="id" name="header_text_secondary" />
   <java-symbol type="id" name="expand_button" />
   <java-symbol type="id" name="expand_button_pill" />
+  <java-symbol type="id" name="expand_button_pill_colorized_layer" />
   <java-symbol type="id" name="expand_button_number" />
   <java-symbol type="id" name="expand_button_icon" />
   <java-symbol type="id" name="alternate_expand_target" />
@@ -3099,6 +3100,7 @@
   <java-symbol type="string" name="status_bar_sync_failing" />
   <java-symbol type="string" name="status_bar_sync_active" />
   <java-symbol type="string" name="status_bar_cast" />
+  <java-symbol type="string" name="status_bar_connected_display" />
   <java-symbol type="string" name="status_bar_hotspot" />
   <java-symbol type="string" name="status_bar_location" />
   <java-symbol type="string" name="status_bar_bluetooth" />
diff --git a/core/tests/coretests/src/android/widget/EditTextCursorAnchorInfoTest.java b/core/tests/coretests/src/android/widget/EditTextCursorAnchorInfoTest.java
index 1a01987..62adc20 100644
--- a/core/tests/coretests/src/android/widget/EditTextCursorAnchorInfoTest.java
+++ b/core/tests/coretests/src/android/widget/EditTextCursorAnchorInfoTest.java
@@ -30,6 +30,7 @@
 import android.view.Gravity;
 import android.view.View;
 import android.view.inputmethod.CursorAnchorInfo;
+import android.view.inputmethod.EditorBoundsInfo;
 
 import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.platform.app.InstrumentationRegistry;
@@ -54,8 +55,15 @@
     private static final int[] sLocationOnScreen = new int[2];
     private static Typeface sTypeface;
     private static final float TEXT_SIZE = 1f;
-    // The line height of the test font font is 1.2 * textSize.
+    // The line height of the test font is 1.2 * textSize.
     private static final int LINE_HEIGHT = 12;
+    private static final int HW_BOUNDS_OFFSET_LEFT = 10;
+    private static final int HW_BOUNDS_OFFSET_TOP = 20;
+    private static final int HW_BOUNDS_OFFSET_RIGHT = 30;
+    private static final int HW_BOUNDS_OFFSET_BOTTOM = 40;
+
+
+    // Default text has 5 lines of text. The needed width is 50px and the needed height is 60px.
     private static final CharSequence DEFAULT_TEXT = "X\nXX\nXXX\nXXXX\nXXXXX";
     private static final ImmutableList<RectF> DEFAULT_LINE_BOUNDS = ImmutableList.of(
             new RectF(0f, 0f, 10f, LINE_HEIGHT),
@@ -131,6 +139,55 @@
     }
 
     @Test
+    public void testEditorBoundsInfo_allVisible() {
+        // The needed width and height of the DEFAULT_TEXT are 50 px and 60 px respectfully.
+        int width = 100;
+        int height = 200;
+        setupEditText(DEFAULT_TEXT, width, height);
+        CursorAnchorInfo cursorAnchorInfo =
+                mEditText.getCursorAnchorInfo(0, sCursorAnchorInfoBuilder, sMatrix);
+        EditorBoundsInfo editorBoundsInfo = cursorAnchorInfo.getEditorBoundsInfo();
+        assertThat(editorBoundsInfo).isNotNull();
+        assertThat(editorBoundsInfo.getEditorBounds()).isEqualTo(new RectF(0, 0, width, height));
+        assertThat(editorBoundsInfo.getHandwritingBounds())
+                .isEqualTo(new RectF(-HW_BOUNDS_OFFSET_LEFT, -HW_BOUNDS_OFFSET_TOP,
+                        width + HW_BOUNDS_OFFSET_RIGHT, height + HW_BOUNDS_OFFSET_BOTTOM));
+    }
+
+    @Test
+    public void testEditorBoundsInfo_scrolled() {
+        // The height of the editor will be 60 px.
+        int width = 100;
+        int visibleTop = 10;
+        int visibleBottom = 30;
+        setupVerticalClippedEditText(width, visibleTop, visibleBottom);
+        CursorAnchorInfo cursorAnchorInfo =
+                mEditText.getCursorAnchorInfo(0, sCursorAnchorInfoBuilder, sMatrix);
+        EditorBoundsInfo editorBoundsInfo = cursorAnchorInfo.getEditorBoundsInfo();
+        assertThat(editorBoundsInfo).isNotNull();
+        assertThat(editorBoundsInfo.getEditorBounds())
+                .isEqualTo(new RectF(0, visibleTop, width, visibleBottom));
+        assertThat(editorBoundsInfo.getHandwritingBounds())
+                .isEqualTo(new RectF(-HW_BOUNDS_OFFSET_LEFT, visibleTop - HW_BOUNDS_OFFSET_TOP,
+                        width + HW_BOUNDS_OFFSET_RIGHT, visibleBottom + HW_BOUNDS_OFFSET_BOTTOM));
+    }
+
+    @Test
+    public void testEditorBoundsInfo_invisible() {
+        // The height of the editor will be 60px. Scroll it to 70px will make it invisible.
+        int width = 100;
+        int visibleTop = 70;
+        int visibleBottom = 70;
+        setupVerticalClippedEditText(width, visibleTop, visibleBottom);
+        CursorAnchorInfo cursorAnchorInfo =
+                mEditText.getCursorAnchorInfo(0, sCursorAnchorInfoBuilder, sMatrix);
+        EditorBoundsInfo editorBoundsInfo = cursorAnchorInfo.getEditorBoundsInfo();
+        assertThat(editorBoundsInfo).isNotNull();
+        assertThat(editorBoundsInfo.getEditorBounds()).isEqualTo(new RectF(0, 0, 0, 0));
+        assertThat(editorBoundsInfo.getHandwritingBounds()).isEqualTo(new RectF(0, 0, 0, 0));
+    }
+
+    @Test
     public void testVisibleLineBounds_allVisible() {
         setupEditText(DEFAULT_TEXT, /* height= */ 100);
         CursorAnchorInfo cursorAnchorInfo =
@@ -465,32 +522,26 @@
     }
 
     private void setupVerticalClippedEditText(int visibleTop, int visibleBottom) {
+        setupVerticalClippedEditText(1000, visibleTop, visibleBottom);
+    }
+
+    /**
+     * Helper method to create an EditText in a vertical ScrollView so that its visible bounds
+     * is Rect(0, visibleTop, width, visibleBottom) in the EditText's coordinates. Both ScrollView
+     * and EditText's width is set to the given width.
+     */
+    private void setupVerticalClippedEditText(int width, int visibleTop, int visibleBottom) {
         ScrollView scrollView = new ScrollView(mActivity);
-        mEditText = new EditText(mActivity);
-        mEditText.setTypeface(sTypeface);
-        mEditText.setText(DEFAULT_TEXT);
-        mEditText.setTextSize(TypedValue.COMPLEX_UNIT_PX, TEXT_SIZE);
-
-        mEditText.setPadding(0, 0, 0, 0);
-        mEditText.setCompoundDrawables(null, null, null, null);
-        mEditText.setCompoundDrawablePadding(0);
-
-        mEditText.scrollTo(0, 0);
-        mEditText.setLineSpacing(0f, 1f);
-
-        // Place the text layout top to the view's top.
-        mEditText.setGravity(Gravity.TOP);
-        int width = 1000;
-        int height = visibleBottom - visibleTop;
+        createEditText();
+        int scrollViewHeight = visibleBottom - visibleTop;
 
         scrollView.addView(mEditText, new FrameLayout.LayoutParams(
                 View.MeasureSpec.makeMeasureSpec(width, View.MeasureSpec.EXACTLY),
                 View.MeasureSpec.makeMeasureSpec(5 * LINE_HEIGHT, View.MeasureSpec.EXACTLY)));
         scrollView.measure(
                 View.MeasureSpec.makeMeasureSpec(width, View.MeasureSpec.EXACTLY),
-                View.MeasureSpec.makeMeasureSpec(height, View.MeasureSpec.EXACTLY));
-        scrollView.layout(0, 0, width, height);
-
+                View.MeasureSpec.makeMeasureSpec(scrollViewHeight, View.MeasureSpec.EXACTLY));
+        scrollView.layout(0, 0, width, scrollViewHeight);
         scrollView.scrollTo(0, visibleTop);
     }
 
@@ -499,6 +550,11 @@
         measureEditText(height);
     }
 
+    private void setupEditText(CharSequence text, int width, int height) {
+        createEditText(text);
+        measureEditText(width, height);
+    }
+
     private void setupEditText(CharSequence text, int height, float lineSpacing,
             float lineMultiplier) {
         createEditText(text);
@@ -537,6 +593,8 @@
         mEditText.setTypeface(sTypeface);
         mEditText.setText(text);
         mEditText.setTextSize(TypedValue.COMPLEX_UNIT_PX, TEXT_SIZE);
+        mEditText.setHandwritingBoundsOffsets(HW_BOUNDS_OFFSET_LEFT, HW_BOUNDS_OFFSET_TOP,
+                HW_BOUNDS_OFFSET_RIGHT, HW_BOUNDS_OFFSET_BOTTOM);
 
         mEditText.setPadding(0, 0, 0, 0);
         mEditText.setCompoundDrawables(null, null, null, null);
diff --git a/libs/WindowManager/Shell/res/values/dimen.xml b/libs/WindowManager/Shell/res/values/dimen.xml
index 2e3f604..14e8253 100644
--- a/libs/WindowManager/Shell/res/values/dimen.xml
+++ b/libs/WindowManager/Shell/res/values/dimen.xml
@@ -419,4 +419,8 @@
     <dimen name="freeform_resize_handle">15dp</dimen>
 
     <dimen name="freeform_resize_corner">44dp</dimen>
+
+    <!-- The height of the area at the top of the screen where a freeform task will transition to
+    fullscreen if dragged until the top bound of the task is within the area. -->
+    <dimen name="desktop_mode_transition_area_height">16dp</dimen>
 </resources>
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 65a35b2..4fda4b7 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
@@ -100,6 +100,10 @@
         }
     }
 
+    private val transitionAreaHeight
+        get() = context.resources.getDimensionPixelSize(
+                com.android.wm.shell.R.dimen.desktop_mode_transition_area_height)
+
     init {
         desktopMode = DesktopModeImpl()
         if (DesktopModeStatus.isProto2Enabled()) {
@@ -700,13 +704,12 @@
             y: Float
     ) {
         if (taskInfo.windowingMode == WINDOWING_MODE_FREEFORM) {
-            val statusBarHeight = getStatusBarHeight(taskInfo)
-            if (y <= statusBarHeight && visualIndicator == null) {
+            if (y <= transitionAreaHeight && visualIndicator == null) {
                 visualIndicator = DesktopModeVisualIndicator(syncQueue, taskInfo,
                         displayController, context, taskSurface, shellTaskOrganizer,
                         rootTaskDisplayAreaOrganizer)
                 visualIndicator?.createFullscreenIndicatorWithAnimatedBounds()
-            } else if (y > statusBarHeight && visualIndicator != null) {
+            } else if (y > transitionAreaHeight && visualIndicator != null) {
                 releaseVisualIndicator()
             }
         }
@@ -726,8 +729,7 @@
             y: Float,
             windowDecor: DesktopModeWindowDecoration
     ) {
-        val statusBarHeight = getStatusBarHeight(taskInfo)
-        if (y <= statusBarHeight && taskInfo.windowingMode == WINDOWING_MODE_FREEFORM) {
+        if (y <= transitionAreaHeight && taskInfo.windowingMode == WINDOWING_MODE_FREEFORM) {
             windowDecor.incrementRelayoutBlock()
             moveToFullscreenWithAnimation(taskInfo, position)
         }
@@ -746,9 +748,9 @@
             taskSurface: SurfaceControl,
             y: Float
     ) {
-        // If the motion event is above the status bar, return since we do not need to show the
-        // visual indicator at this point.
-        if (y < getStatusBarHeight(taskInfo)) {
+        // If the motion event is above the status bar and the visual indicator is not yet visible,
+        // return since we do not need to show the visual indicator at this point.
+        if (y < getStatusBarHeight(taskInfo) && visualIndicator == null) {
             return
         }
         if (visualIndicator == null) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
index d8a8877..7565996 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
@@ -27,6 +27,7 @@
 import static android.view.WindowManager.TRANSIT_TO_BACK;
 import static android.view.WindowManager.TRANSIT_TO_FRONT;
 import static android.view.WindowManager.fixScale;
+import static android.window.TransitionInfo.FLAG_IS_BEHIND_STARTING_WINDOW;
 import static android.window.TransitionInfo.FLAG_IS_OCCLUDED;
 import static android.window.TransitionInfo.FLAG_IS_WALLPAPER;
 import static android.window.TransitionInfo.FLAG_MOVED_TO_TOP;
@@ -728,11 +729,15 @@
         final int changeSize = info.getChanges().size();
         boolean taskChange = false;
         boolean transferStartingWindow = false;
+        int noAnimationBehindStartingWindow = 0;
         boolean allOccluded = changeSize > 0;
         for (int i = changeSize - 1; i >= 0; --i) {
             final TransitionInfo.Change change = info.getChanges().get(i);
             taskChange |= change.getTaskInfo() != null;
             transferStartingWindow |= change.hasFlags(FLAG_STARTING_WINDOW_TRANSFER_RECIPIENT);
+            if (change.hasAllFlags(FLAG_IS_BEHIND_STARTING_WINDOW | FLAG_NO_ANIMATION)) {
+                noAnimationBehindStartingWindow++;
+            }
             if (!change.hasFlags(FLAG_IS_OCCLUDED)) {
                 allOccluded = false;
             }
@@ -740,9 +745,11 @@
         // There does not need animation when:
         // A. Transfer starting window. Apply transfer starting window directly if there is no other
         // task change. Since this is an activity->activity situation, we can detect it by selecting
-        // transitions with only 2 changes where neither are tasks and one is a starting-window
-        // recipient.
-        if (!taskChange && transferStartingWindow && changeSize == 2
+        // transitions with only 2 changes where
+        // 1. neither are tasks, and
+        // 2. one is a starting-window recipient, or all change is behind starting window.
+        if (!taskChange && (transferStartingWindow || noAnimationBehindStartingWindow == changeSize)
+                && changeSize == 2
                 // B. It's visibility change if the TRANSIT_TO_BACK/TO_FRONT happened when all
                 // changes are underneath another change.
                 || ((info.getType() == TRANSIT_TO_BACK || info.getType() == TRANSIT_TO_FRONT)
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java
index 92c2a7c..cf16920 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java
@@ -193,7 +193,7 @@
 
         final DragPositioningCallback dragPositioningCallback =
                 new FluidResizeTaskPositioner(mTaskOrganizer, windowDecoration, mDisplayController,
-                        null /* disallowedAreaForEndBounds */);
+                        0 /* disallowedAreaForEndBoundsHeight */);
         final CaptionTouchEventListener touchEventListener =
                 new CaptionTouchEventListener(taskInfo, dragPositioningCallback);
         windowDecoration.setCaptionListeners(touchEventListener, touchEventListener);
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 331835c..7245bc9 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
@@ -845,7 +845,7 @@
         windowDecoration.createResizeVeil();
 
         final DragPositioningCallback dragPositioningCallback = createDragPositioningCallback(
-                windowDecoration, taskInfo);
+                windowDecoration);
         final DesktopModeTouchEventListener touchEventListener =
                 new DesktopModeTouchEventListener(taskInfo, dragPositioningCallback);
 
@@ -858,24 +858,17 @@
         incrementEventReceiverTasks(taskInfo.displayId);
     }
     private DragPositioningCallback createDragPositioningCallback(
-            @NonNull DesktopModeWindowDecoration windowDecoration,
-            @NonNull RunningTaskInfo taskInfo) {
-        final int screenWidth = mDisplayController.getDisplayLayout(taskInfo.displayId).width();
-        final Rect disallowedAreaForEndBounds;
-        if (DesktopModeStatus.isProto2Enabled()) {
-            disallowedAreaForEndBounds = new Rect(0, 0, screenWidth,
-                    getStatusBarHeight(taskInfo.displayId));
-        } else {
-            disallowedAreaForEndBounds = null;
-        }
+            @NonNull DesktopModeWindowDecoration windowDecoration) {
+        final int transitionAreaHeight = mContext.getResources().getDimensionPixelSize(
+                R.dimen.desktop_mode_transition_area_height);
         if (!DesktopModeStatus.isVeiledResizeEnabled()) {
             return new FluidResizeTaskPositioner(mTaskOrganizer, windowDecoration,
-                    mDisplayController, disallowedAreaForEndBounds, mDragStartListener,
-                    mTransactionFactory);
+                    mDisplayController, mDragStartListener, mTransactionFactory,
+                    transitionAreaHeight);
         } else {
             return new VeiledResizeTaskPositioner(mTaskOrganizer, windowDecoration,
-                    mDisplayController, disallowedAreaForEndBounds, mDragStartListener,
-                    mTransitions);
+                    mDisplayController, mDragStartListener, mTransitions,
+                    transitionAreaHeight);
         }
     }
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragPositioningCallbackUtility.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragPositioningCallbackUtility.java
index 09e29bc..e32bd42 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragPositioningCallbackUtility.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragPositioningCallbackUtility.java
@@ -83,8 +83,6 @@
         // Make sure the new resizing destination in any direction falls within the stable bounds.
         // If not, set the bounds back to the old location that was valid to avoid conflicts with
         // some regions such as the gesture area.
-        displayController.getDisplayLayout(windowDecoration.mDisplay.getDisplayId())
-                .getStableBounds(stableBounds);
         if ((ctrlType & CTRL_TYPE_LEFT) != 0) {
             final int candidateLeft = repositionTaskBounds.left + (int) delta.x;
             repositionTaskBounds.left = (candidateLeft > stableBounds.left)
@@ -136,7 +134,7 @@
                 repositionTaskBounds.top);
     }
 
-    static void updateTaskBounds(Rect repositionTaskBounds, Rect taskBoundsAtDragStart,
+    private static void updateTaskBounds(Rect repositionTaskBounds, Rect taskBoundsAtDragStart,
             PointF repositionStartPoint, float x, float y) {
         final float deltaX = x - repositionStartPoint.x;
         final float deltaY = y - repositionStartPoint.y;
@@ -145,6 +143,23 @@
     }
 
     /**
+     * Updates repositionTaskBounds to the final bounds of the task after the drag is finished. If
+     * the bounds are outside of the stable bounds, they are shifted to place task at the top of the
+     * stable bounds.
+     */
+    static void onDragEnd(Rect repositionTaskBounds, Rect taskBoundsAtDragStart, Rect stableBounds,
+            PointF repositionStartPoint, float x, float y)  {
+        updateTaskBounds(repositionTaskBounds, taskBoundsAtDragStart, repositionStartPoint,
+                x, y);
+
+        // If task is outside of stable bounds (in the status bar area), shift the task down.
+        if (stableBounds.top > repositionTaskBounds.top) {
+            final int yShift =  stableBounds.top - repositionTaskBounds.top;
+            repositionTaskBounds.offset(0, yShift);
+        }
+    }
+
+    /**
      * Apply a bounds change to a task.
      * @param windowDecoration decor of task we are changing bounds for
      * @param taskBounds new bounds of this task
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/FluidResizeTaskPositioner.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/FluidResizeTaskPositioner.java
index 9082323..917abf5 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/FluidResizeTaskPositioner.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/FluidResizeTaskPositioner.java
@@ -21,8 +21,6 @@
 import android.view.SurfaceControl;
 import android.window.WindowContainerTransaction;
 
-import androidx.annotation.Nullable;
-
 import com.android.wm.shell.ShellTaskOrganizer;
 import com.android.wm.shell.common.DisplayController;
 
@@ -42,28 +40,31 @@
     private final Rect mTaskBoundsAtDragStart = new Rect();
     private final PointF mRepositionStartPoint = new PointF();
     private final Rect mRepositionTaskBounds = new Rect();
-    // If a task move (not resize) finishes in this region, the positioner will not attempt to
+    // If a task move (not resize) finishes with the positions y less than this value, do not
     // finalize the bounds there using WCT#setBounds
-    private final Rect mDisallowedAreaForEndBounds;
+    private final int mDisallowedAreaForEndBoundsHeight;
     private boolean mHasDragResized;
     private int mCtrlType;
 
     FluidResizeTaskPositioner(ShellTaskOrganizer taskOrganizer, WindowDecoration windowDecoration,
-            DisplayController displayController, @Nullable Rect disallowedAreaForEndBounds) {
-        this(taskOrganizer, windowDecoration, displayController, disallowedAreaForEndBounds,
-                dragStartListener -> {}, SurfaceControl.Transaction::new);
+            DisplayController displayController, int disallowedAreaForEndBoundsHeight) {
+        this(taskOrganizer, windowDecoration, displayController, dragStartListener -> {},
+                SurfaceControl.Transaction::new, disallowedAreaForEndBoundsHeight);
     }
 
     FluidResizeTaskPositioner(ShellTaskOrganizer taskOrganizer, WindowDecoration windowDecoration,
-            DisplayController displayController, @Nullable Rect disallowedAreaForEndBounds,
+            DisplayController displayController,
             DragPositioningCallbackUtility.DragStartListener dragStartListener,
-            Supplier<SurfaceControl.Transaction> supplier) {
+            Supplier<SurfaceControl.Transaction> supplier,
+            int disallowedAreaForEndBoundsHeight) {
         mTaskOrganizer = taskOrganizer;
         mWindowDecoration = windowDecoration;
         mDisplayController = displayController;
-        mDisallowedAreaForEndBounds = new Rect(disallowedAreaForEndBounds);
         mDragStartListener = dragStartListener;
         mTransactionSupplier = supplier;
+        mDisallowedAreaForEndBoundsHeight = disallowedAreaForEndBoundsHeight;
+        mDisplayController.getDisplayLayout(windowDecoration.mDisplay.getDisplayId())
+                .getStableBounds(mStableBounds);
     }
 
     @Override
@@ -121,10 +122,10 @@
             }
             mTaskOrganizer.applyTransaction(wct);
         } else if (mCtrlType == CTRL_TYPE_UNDEFINED
-                && !mDisallowedAreaForEndBounds.contains((int) x, (int) y)) {
+                && y > mDisallowedAreaForEndBoundsHeight) {
             final WindowContainerTransaction wct = new WindowContainerTransaction();
-            DragPositioningCallbackUtility.updateTaskBounds(mRepositionTaskBounds,
-                    mTaskBoundsAtDragStart, mRepositionStartPoint, x, y);
+            DragPositioningCallbackUtility.onDragEnd(mRepositionTaskBounds,
+                    mTaskBoundsAtDragStart, mStableBounds, mRepositionStartPoint, x, y);
             wct.setBounds(mWindowDecoration.mTaskInfo.token, mRepositionTaskBounds);
             mTaskOrganizer.applyTransaction(wct);
         }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositioner.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositioner.java
index 39b9021..bf3ff3f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositioner.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositioner.java
@@ -53,33 +53,35 @@
     private final Rect mTaskBoundsAtDragStart = new Rect();
     private final PointF mRepositionStartPoint = new PointF();
     private final Rect mRepositionTaskBounds = new Rect();
-    // If a task move (not resize) finishes in this region, the positioner will not attempt to
+    // If a task move (not resize) finishes with the positions y less than this value, do not
     // finalize the bounds there using WCT#setBounds
-    private final Rect mDisallowedAreaForEndBounds;
+    private final int mDisallowedAreaForEndBoundsHeight;
     private final Supplier<SurfaceControl.Transaction> mTransactionSupplier;
     private int mCtrlType;
 
     public VeiledResizeTaskPositioner(ShellTaskOrganizer taskOrganizer,
             DesktopModeWindowDecoration windowDecoration, DisplayController displayController,
-            Rect disallowedAreaForEndBounds,
             DragPositioningCallbackUtility.DragStartListener dragStartListener,
-            Transitions transitions) {
-        this(taskOrganizer, windowDecoration, displayController, disallowedAreaForEndBounds,
-                dragStartListener, SurfaceControl.Transaction::new, transitions);
+            Transitions transitions,
+            int disallowedAreaForEndBoundsHeight) {
+        this(taskOrganizer, windowDecoration, displayController, dragStartListener,
+                SurfaceControl.Transaction::new, transitions, disallowedAreaForEndBoundsHeight);
     }
 
     public VeiledResizeTaskPositioner(ShellTaskOrganizer taskOrganizer,
             DesktopModeWindowDecoration windowDecoration, DisplayController displayController,
-            Rect disallowedAreaForEndBounds,
             DragPositioningCallbackUtility.DragStartListener dragStartListener,
-            Supplier<SurfaceControl.Transaction> supplier, Transitions transitions) {
+            Supplier<SurfaceControl.Transaction> supplier, Transitions transitions,
+            int disallowedAreaForEndBoundsHeight) {
         mTaskOrganizer = taskOrganizer;
         mDesktopWindowDecoration = windowDecoration;
         mDisplayController = displayController;
         mDragStartListener = dragStartListener;
-        mDisallowedAreaForEndBounds = new Rect(disallowedAreaForEndBounds);
         mTransactionSupplier = supplier;
         mTransitions = transitions;
+        mDisallowedAreaForEndBoundsHeight = disallowedAreaForEndBoundsHeight;
+        mDisplayController.getDisplayLayout(windowDecoration.mDisplay.getDisplayId())
+                .getStableBounds(mStableBounds);
     }
 
     @Override
@@ -110,8 +112,7 @@
         } else if (mCtrlType == CTRL_TYPE_UNDEFINED) {
             final SurfaceControl.Transaction t = mTransactionSupplier.get();
             DragPositioningCallbackUtility.setPositionOnDrag(mDesktopWindowDecoration,
-                    mRepositionTaskBounds, mTaskBoundsAtDragStart, mRepositionStartPoint, t,
-                    x, y);
+                    mRepositionTaskBounds, mTaskBoundsAtDragStart, mRepositionStartPoint, t, x, y);
             t.apply();
         }
     }
@@ -138,9 +139,9 @@
                 // won't be called.
                 mDesktopWindowDecoration.hideResizeVeil();
             }
-        } else if (!mDisallowedAreaForEndBounds.contains((int) x, (int) y)) {
-            DragPositioningCallbackUtility.updateTaskBounds(mRepositionTaskBounds,
-                    mTaskBoundsAtDragStart, mRepositionStartPoint, x, y);
+        } else if (y > mDisallowedAreaForEndBoundsHeight) {
+            DragPositioningCallbackUtility.onDragEnd(mRepositionTaskBounds,
+                    mTaskBoundsAtDragStart, mStableBounds, mRepositionStartPoint, x, y);
             DragPositioningCallbackUtility.applyTaskBoundsChange(new WindowContainerTransaction(),
                     mDesktopWindowDecoration, mRepositionTaskBounds, mTaskOrganizer);
         }
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SplitScreenUtils.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SplitScreenUtils.kt
index 27eaa40..f4828f1 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SplitScreenUtils.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SplitScreenUtils.kt
@@ -326,14 +326,14 @@
         dividerBar.drag(
             Point(
                 if (dragToRight) {
-                    displayBounds.width * 4 / 5
+                    displayBounds.right
                 } else {
-                    displayBounds.width * 1 / 5
+                    displayBounds.left
                 },
                 if (dragToBottom) {
-                    displayBounds.height * 4 / 5
+                    displayBounds.bottom
                 } else {
-                    displayBounds.height * 1 / 5
+                    displayBounds.top
                 }
             )
         )
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.java
index 23158ea..adc2a6f 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.java
@@ -90,6 +90,7 @@
     @Mock private DesktopModeWindowDecorViewModel.InputMonitorFactory mMockInputMonitorFactory;
     @Mock private Supplier<SurfaceControl.Transaction> mTransactionFactory;
     @Mock private SurfaceControl.Transaction mTransaction;
+    @Mock private Display mDisplay;
     private final List<InputManager> mMockInputManagers = new ArrayList<>();
 
     private DesktopModeWindowDecorViewModel mDesktopModeWindowDecorViewModel;
@@ -126,6 +127,9 @@
         final InputChannel[] inputChannels = InputChannel.openInputChannelPair(TAG);
         inputChannels[0].dispose();
         when(mInputMonitor.getInputChannel()).thenReturn(inputChannels[1]);
+
+        mDesktopModeWindowDecoration.mDisplay = mDisplay;
+        doReturn(Display.DEFAULT_DISPLAY).when(mDisplay).getDisplayId();
     }
 
     @Test
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/FluidResizeTaskPositionerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/FluidResizeTaskPositionerTest.kt
index 69604dd..6f0599a 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/FluidResizeTaskPositionerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/FluidResizeTaskPositionerTest.kt
@@ -22,6 +22,7 @@
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.eq
 import org.mockito.Mock
 import org.mockito.Mockito.any
 import org.mockito.Mockito.argThat
@@ -71,16 +72,6 @@
     fun setUp() {
         MockitoAnnotations.initMocks(this)
 
-        taskPositioner =
-            FluidResizeTaskPositioner(
-                mockShellTaskOrganizer,
-                mockWindowDecoration,
-                mockDisplayController,
-                DISALLOWED_AREA_FOR_END_BOUNDS,
-                mockDragStartListener,
-                mockTransactionFactory
-        )
-
         whenever(taskToken.asBinder()).thenReturn(taskBinder)
         whenever(mockDisplayController.getDisplayLayout(DISPLAY_ID)).thenReturn(mockDisplayLayout)
         whenever(mockDisplayLayout.densityDpi()).thenReturn(DENSITY_DPI)
@@ -101,6 +92,15 @@
         }
         mockWindowDecoration.mDisplay = mockDisplay
         whenever(mockDisplay.displayId).thenAnswer { DISPLAY_ID }
+
+        taskPositioner = FluidResizeTaskPositioner(
+                mockShellTaskOrganizer,
+                mockWindowDecoration,
+                mockDisplayController,
+                mockDragStartListener,
+                mockTransactionFactory,
+                DISALLOWED_AREA_FOR_END_BOUNDS_HEIGHT
+        )
     }
 
     @Test
@@ -544,7 +544,7 @@
         )
 
         val newX = STARTING_BOUNDS.right.toFloat() + 5
-        val newY = STARTING_BOUNDS.top.toFloat() + 5
+        val newY = DISALLOWED_AREA_FOR_END_BOUNDS_HEIGHT.toFloat() - 1
         taskPositioner.onDragPositioningMove(
                 newX,
                 newY
@@ -614,6 +614,38 @@
         })
     }
 
+    @Test
+    fun testDragResize_drag_taskPositionedInStableBounds() {
+        taskPositioner.onDragPositioningStart(
+                CTRL_TYPE_UNDEFINED, // drag
+                STARTING_BOUNDS.left.toFloat(),
+                STARTING_BOUNDS.top.toFloat()
+        )
+
+        val newX = STARTING_BOUNDS.left.toFloat()
+        val newY = STABLE_BOUNDS.top.toFloat() - 5
+        taskPositioner.onDragPositioningMove(
+                newX,
+                newY
+        )
+        verify(mockTransaction).setPosition(any(), eq(newX), eq(newY))
+
+        taskPositioner.onDragPositioningEnd(
+                newX,
+                newY
+        )
+        // Verify task's top bound is set to stable bounds top since dragged outside stable bounds
+        // but not in disallowed end bounds area.
+        verify(mockShellTaskOrganizer).applyTransaction(argThat { wct ->
+            return@argThat wct.changes.any { (token, change) ->
+                token == taskBinder &&
+                        (change.windowSetMask and WindowConfiguration.WINDOW_CONFIG_BOUNDS) != 0 &&
+                        change.configuration.windowConfiguration.bounds.top ==
+                        STABLE_BOUNDS.top
+            }
+        })
+    }
+
     companion object {
         private const val TASK_ID = 5
         private const val MIN_WIDTH = 10
@@ -622,10 +654,11 @@
         private const val DEFAULT_MIN = 40
         private const val DISPLAY_ID = 1
         private const val NAVBAR_HEIGHT = 50
+        private const val CAPTION_HEIGHT = 50
+        private const val DISALLOWED_AREA_FOR_END_BOUNDS_HEIGHT = 10
         private val DISPLAY_BOUNDS = Rect(0, 0, 2400, 1600)
-        private val STARTING_BOUNDS = Rect(0, 0, 100, 100)
+        private val STARTING_BOUNDS = Rect(100, 100, 200, 200)
         private val STABLE_INSETS = Rect(0, 50, 0, 0)
-        private val DISALLOWED_AREA_FOR_END_BOUNDS = Rect(0, 0, 300, 300)
         private val DISALLOWED_RESIZE_AREA = Rect(
                 DISPLAY_BOUNDS.left,
                 DISPLAY_BOUNDS.bottom - NAVBAR_HEIGHT,
@@ -633,7 +666,7 @@
                 DISPLAY_BOUNDS.bottom)
         private val STABLE_BOUNDS = Rect(
                 DISPLAY_BOUNDS.left,
-                DISPLAY_BOUNDS.top,
+                DISPLAY_BOUNDS.top + CAPTION_HEIGHT,
                 DISPLAY_BOUNDS.right,
                 DISPLAY_BOUNDS.bottom - NAVBAR_HEIGHT
         )
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositionerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositionerTest.kt
index 4147dd8..3465ddd 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositionerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositionerTest.kt
@@ -89,17 +89,6 @@
     fun setUp() {
         MockitoAnnotations.initMocks(this)
 
-        taskPositioner =
-            VeiledResizeTaskPositioner(
-                mockShellTaskOrganizer,
-                mockDesktopWindowDecoration,
-                mockDisplayController,
-                DISALLOWED_AREA_FOR_END_BOUNDS,
-                mockDragStartListener,
-                mockTransactionFactory,
-                mockTransitions
-            )
-
         whenever(taskToken.asBinder()).thenReturn(taskBinder)
         whenever(mockDisplayController.getDisplayLayout(DISPLAY_ID)).thenReturn(mockDisplayLayout)
         whenever(mockDisplayLayout.densityDpi()).thenReturn(DENSITY_DPI)
@@ -119,6 +108,17 @@
         }
         mockDesktopWindowDecoration.mDisplay = mockDisplay
         whenever(mockDisplay.displayId).thenAnswer { DISPLAY_ID }
+
+        taskPositioner =
+                VeiledResizeTaskPositioner(
+                        mockShellTaskOrganizer,
+                        mockDesktopWindowDecoration,
+                        mockDisplayController,
+                        mockDragStartListener,
+                        mockTransactionFactory,
+                        mockTransitions,
+                        DISALLOWED_AREA_FOR_END_BOUNDS_HEIGHT
+                )
     }
 
     @Test
@@ -269,7 +269,7 @@
         )
 
         val newX = STARTING_BOUNDS.left.toFloat() + 5
-        val newY = STARTING_BOUNDS.top.toFloat() + 5
+        val newY = DISALLOWED_AREA_FOR_END_BOUNDS_HEIGHT.toFloat() - 1
         taskPositioner.onDragPositioningMove(
                 newX,
                 newY
@@ -334,6 +334,38 @@
         })
     }
 
+    @Test
+    fun testDragResize_drag_taskPositionedInStableBounds() {
+        taskPositioner.onDragPositioningStart(
+                CTRL_TYPE_UNDEFINED, // drag
+                STARTING_BOUNDS.left.toFloat(),
+                STARTING_BOUNDS.top.toFloat()
+        )
+
+        val newX = STARTING_BOUNDS.left.toFloat()
+        val newY = STABLE_BOUNDS.top.toFloat() - 5
+        taskPositioner.onDragPositioningMove(
+                newX,
+                newY
+        )
+        verify(mockTransaction).setPosition(any(), eq(newX), eq(newY))
+
+        taskPositioner.onDragPositioningEnd(
+                newX,
+                newY
+        )
+        // Verify task's top bound is set to stable bounds top since dragged outside stable bounds
+        // but not in disallowed end bounds area.
+        verify(mockShellTaskOrganizer).applyTransaction(argThat { wct ->
+            return@argThat wct.changes.any { (token, change) ->
+                token == taskBinder &&
+                        (change.windowSetMask and WindowConfiguration.WINDOW_CONFIG_BOUNDS) != 0 &&
+                        change.configuration.windowConfiguration.bounds.top ==
+                        STABLE_BOUNDS.top
+            }
+        })
+    }
+
     companion object {
         private const val TASK_ID = 5
         private const val MIN_WIDTH = 10
@@ -342,12 +374,13 @@
         private const val DEFAULT_MIN = 40
         private const val DISPLAY_ID = 1
         private const val NAVBAR_HEIGHT = 50
+        private const val CAPTION_HEIGHT = 50
+        private const val DISALLOWED_AREA_FOR_END_BOUNDS_HEIGHT = 10
         private val DISPLAY_BOUNDS = Rect(0, 0, 2400, 1600)
-        private val STARTING_BOUNDS = Rect(0, 0, 100, 100)
-        private val DISALLOWED_AREA_FOR_END_BOUNDS = Rect(0, 0, 50, 50)
+        private val STARTING_BOUNDS = Rect(100, 100, 200, 200)
         private val STABLE_BOUNDS = Rect(
             DISPLAY_BOUNDS.left,
-            DISPLAY_BOUNDS.top,
+            DISPLAY_BOUNDS.top + CAPTION_HEIGHT,
             DISPLAY_BOUNDS.right,
             DISPLAY_BOUNDS.bottom - NAVBAR_HEIGHT
         )
diff --git a/libs/hwui/pipeline/skia/ShaderCache.cpp b/libs/hwui/pipeline/skia/ShaderCache.cpp
index 00919dc..f71e728 100644
--- a/libs/hwui/pipeline/skia/ShaderCache.cpp
+++ b/libs/hwui/pipeline/skia/ShaderCache.cpp
@@ -79,7 +79,7 @@
 
 void ShaderCache::initShaderDiskCache(const void* identity, ssize_t size) {
     ATRACE_NAME("initShaderDiskCache");
-    std::lock_guard<std::mutex> lock(mMutex);
+    std::lock_guard lock(mMutex);
 
     // Emulators can switch between different renders either as part of config
     // or snapshot migration. Also, program binaries may not work well on some
@@ -92,7 +92,7 @@
 }
 
 void ShaderCache::setFilename(const char* filename) {
-    std::lock_guard<std::mutex> lock(mMutex);
+    std::lock_guard lock(mMutex);
     mFilename = filename;
 }
 
@@ -104,7 +104,7 @@
 sk_sp<SkData> ShaderCache::load(const SkData& key) {
     ATRACE_NAME("ShaderCache::load");
     size_t keySize = key.size();
-    std::lock_guard<std::mutex> lock(mMutex);
+    std::lock_guard lock(mMutex);
     if (!mInitialized) {
         return nullptr;
     }
@@ -181,13 +181,18 @@
             auto key = sIDKey;
             set(mBlobCache.get(), &key, sizeof(key), mIDHash.data(), mIDHash.size());
         }
+        // The most straightforward way to make ownership shared
+        mMutex.unlock();
+        mMutex.lock_shared();
         mBlobCache->writeToFile();
+        mMutex.unlock_shared();
+        mMutex.lock();
     }
 }
 
 void ShaderCache::store(const SkData& key, const SkData& data, const SkString& /*description*/) {
     ATRACE_NAME("ShaderCache::store");
-    std::lock_guard<std::mutex> lock(mMutex);
+    std::lock_guard lock(mMutex);
     mNumShadersCachedInRam++;
     ATRACE_FORMAT("HWUI RAM cache: %d shaders", mNumShadersCachedInRam);
 
@@ -229,7 +234,7 @@
         mSavePending = true;
         std::thread deferredSaveThread([this]() {
             usleep(mDeferredSaveDelayMs * 1000);  // milliseconds to microseconds
-            std::lock_guard<std::mutex> lock(mMutex);
+            std::lock_guard lock(mMutex);
             // Store file on disk if there a new shader or Vulkan pipeline cache size changed.
             if (mCacheDirty || mNewPipelineCacheSize != mOldPipelineCacheSize) {
                 saveToDiskLocked();
@@ -245,11 +250,12 @@
 
 void ShaderCache::onVkFrameFlushed(GrDirectContext* context) {
     {
-        std::lock_guard<std::mutex> lock(mMutex);
-
+        mMutex.lock_shared();
         if (!mInitialized || !mTryToStorePipelineCache) {
+            mMutex.unlock_shared();
             return;
         }
+        mMutex.unlock_shared();
     }
     mInStoreVkPipelineInProgress = true;
     context->storeVkPipelineCacheData();
diff --git a/libs/hwui/pipeline/skia/ShaderCache.h b/libs/hwui/pipeline/skia/ShaderCache.h
index f5506d6..2f91c77 100644
--- a/libs/hwui/pipeline/skia/ShaderCache.h
+++ b/libs/hwui/pipeline/skia/ShaderCache.h
@@ -19,8 +19,10 @@
 #include <GrContextOptions.h>
 #include <SkRefCnt.h>
 #include <cutils/compiler.h>
+#include <ftl/shared_mutex.h>
+#include <utils/Mutex.h>
+
 #include <memory>
-#include <mutex>
 #include <string>
 #include <vector>
 
@@ -99,20 +101,20 @@
      * this will do so, loading the serialized cache contents from disk if
      * possible.
      */
-    BlobCache* getBlobCacheLocked();
+    BlobCache* getBlobCacheLocked() REQUIRES(mMutex);
 
     /**
      * "validateCache" updates the cache to match the given identity.  If the
      * cache currently has the wrong identity, all entries in the cache are cleared.
      */
-    bool validateCache(const void* identity, ssize_t size);
+    bool validateCache(const void* identity, ssize_t size) REQUIRES(mMutex);
 
     /**
-     * "saveToDiskLocked" attemps to save the current contents of the cache to
+     * "saveToDiskLocked" attempts to save the current contents of the cache to
      * disk. If the identity hash exists, we will insert the identity hash into
      * the cache for next validation.
      */
-    void saveToDiskLocked();
+    void saveToDiskLocked() REQUIRES(mMutex);
 
     /**
      * "mInitialized" indicates whether the ShaderCache is in the initialized
@@ -122,7 +124,7 @@
      * the load and store methods will return without performing any cache
      * operations.
      */
-    bool mInitialized = false;
+    bool mInitialized GUARDED_BY(mMutex) = false;
 
     /**
      * "mBlobCache" is the cache in which the key/value blob pairs are stored.  It
@@ -131,7 +133,7 @@
      * The blob cache contains the Android build number. We treat version mismatches as an empty
      * cache (logic implemented in BlobCache::unflatten).
      */
-    std::unique_ptr<FileBlobCache> mBlobCache;
+    std::unique_ptr<FileBlobCache> mBlobCache GUARDED_BY(mMutex);
 
     /**
      * "mFilename" is the name of the file for storing cache contents in between
@@ -140,7 +142,7 @@
      * empty string indicates that the cache should not be saved to or restored
      * from disk.
      */
-    std::string mFilename;
+    std::string mFilename GUARDED_BY(mMutex);
 
     /**
      * "mIDHash" is the current identity hash for the cache validation. It is
@@ -149,7 +151,7 @@
      * indicates that cache validation is not performed, and the hash should
      * not be stored on disk.
      */
-    std::vector<uint8_t> mIDHash;
+    std::vector<uint8_t> mIDHash GUARDED_BY(mMutex);
 
     /**
      * "mSavePending" indicates whether or not a deferred save operation is
@@ -159,7 +161,7 @@
      * contents to disk, unless mDeferredSaveDelayMs is 0 in which case saving
      * is disabled.
      */
-    bool mSavePending = false;
+    bool mSavePending GUARDED_BY(mMutex) = false;
 
     /**
      *  "mObservedBlobValueSize" is the maximum value size observed by the cache reading function.
@@ -174,16 +176,16 @@
     unsigned int mDeferredSaveDelayMs = 4 * 1000;
 
     /**
-     * "mMutex" is the mutex used to prevent concurrent access to the member
+     * "mMutex" is the shared mutex used to prevent concurrent access to the member
      * variables. It must be locked whenever the member variables are accessed.
      */
-    mutable std::mutex mMutex;
+    mutable ftl::SharedMutex mMutex;
 
     /**
      *  If set to "true", the next call to onVkFrameFlushed, will invoke
      * GrCanvas::storeVkPipelineCacheData. This does not guarantee that data will be stored on disk.
      */
-    bool mTryToStorePipelineCache = true;
+    bool mTryToStorePipelineCache GUARDED_BY(mMutex) = true;
 
     /**
      * This flag is used by "ShaderCache::store" to distinguish between shader data and
@@ -195,16 +197,16 @@
      *  "mNewPipelineCacheSize" has the size of the new Vulkan pipeline cache data. It is used
      *  to prevent unnecessary disk writes, if the pipeline cache size has not changed.
      */
-    size_t mNewPipelineCacheSize = -1;
+    size_t mNewPipelineCacheSize GUARDED_BY(mMutex) = -1;
     /**
      *  "mOldPipelineCacheSize" has the size of the Vulkan pipeline cache data stored on disk.
      */
-    size_t mOldPipelineCacheSize = -1;
+    size_t mOldPipelineCacheSize GUARDED_BY(mMutex) = -1;
 
     /**
      *  "mCacheDirty" is true when there is new shader cache data, which is not saved to disk.
      */
-    bool mCacheDirty = false;
+    bool mCacheDirty GUARDED_BY(mMutex) = false;
 
     /**
      * "sCache" is the singleton ShaderCache object.
@@ -221,7 +223,7 @@
      * interesting to keep track of how many shaders are stored in RAM. This
      * class provides a convenient entry point for that.
      */
-    int mNumShadersCachedInRam = 0;
+    int mNumShadersCachedInRam GUARDED_BY(mMutex) = 0;
 
     friend class ShaderCacheTestUtils;  // used for unit testing
 };
diff --git a/libs/hwui/tests/unit/ShaderCacheTests.cpp b/libs/hwui/tests/unit/ShaderCacheTests.cpp
index 7bcd45c..9aa2e1d 100644
--- a/libs/hwui/tests/unit/ShaderCacheTests.cpp
+++ b/libs/hwui/tests/unit/ShaderCacheTests.cpp
@@ -49,7 +49,7 @@
      */
     static void reinitializeAllFields(ShaderCache& cache) {
         ShaderCache newCache = ShaderCache();
-        std::lock_guard<std::mutex> lock(cache.mMutex);
+        std::lock_guard lock(cache.mMutex), newLock(newCache.mMutex);
         // By order of declaration
         cache.mInitialized = newCache.mInitialized;
         cache.mBlobCache.reset(nullptr);
@@ -72,7 +72,7 @@
      * manually, as seen in the "terminate" testing helper function.
      */
     static void setSaveDelayMs(ShaderCache& cache, unsigned int saveDelayMs) {
-        std::lock_guard<std::mutex> lock(cache.mMutex);
+        std::lock_guard lock(cache.mMutex);
         cache.mDeferredSaveDelayMs = saveDelayMs;
     }
 
@@ -81,7 +81,7 @@
      * Next call to "initShaderDiskCache" will load again the in-memory cache from disk.
      */
     static void terminate(ShaderCache& cache, bool saveContent) {
-        std::lock_guard<std::mutex> lock(cache.mMutex);
+        std::lock_guard lock(cache.mMutex);
         if (saveContent) {
             cache.saveToDiskLocked();
         }
@@ -93,6 +93,7 @@
      */
     template <typename T>
     static bool validateCache(ShaderCache& cache, std::vector<T> hash) {
+        std::lock_guard lock(cache.mMutex);
         return cache.validateCache(hash.data(), hash.size() * sizeof(T));
     }
 
@@ -108,7 +109,7 @@
      */
     static void waitForPendingSave(ShaderCache& cache, const int timeoutMs = 50) {
         {
-            std::lock_guard<std::mutex> lock(cache.mMutex);
+            std::lock_guard lock(cache.mMutex);
             ASSERT_TRUE(cache.mSavePending);
         }
         bool saving = true;
@@ -123,7 +124,7 @@
             usleep(delayMicroseconds);
             elapsedMilliseconds += (float)delayMicroseconds / 1000;
 
-            std::lock_guard<std::mutex> lock(cache.mMutex);
+            std::lock_guard lock(cache.mMutex);
             saving = cache.mSavePending;
         }
     }
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/CustomizedAppBar.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/CustomizedAppBar.kt
index e07a629..90a723f 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/CustomizedAppBar.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/CustomizedAppBar.kt
@@ -50,7 +50,7 @@
 import androidx.compose.runtime.SideEffect
 import androidx.compose.runtime.Stable
 import androidx.compose.runtime.getValue
-import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.mutableFloatStateOf
 import androidx.compose.runtime.remember
 import androidx.compose.runtime.rememberUpdatedState
 import androidx.compose.ui.Alignment
@@ -286,21 +286,22 @@
         )
     }
     val pinnedHeightPx: Float
-    val density = LocalDensity.current
-    val maxHeightPx = density.run {
-        remember { mutableStateOf((MaxHeightWithoutTitle + DefaultTitleHeight).toPx()) }
-    }
     val titleBottomPaddingPx: Int
+    val defaultMaxHeightPx: Float
+    val density = LocalDensity.current
     density.run {
         pinnedHeightPx = pinnedHeight.toPx()
         titleBottomPaddingPx = titleBottomPadding.roundToPx()
+        defaultMaxHeightPx = (MaxHeightWithoutTitle + DefaultTitleHeight).toPx()
     }
 
+    val maxHeightPx = remember(density) { mutableFloatStateOf(defaultMaxHeightPx) }
+
     // Sets the app bar's height offset limit to hide just the bottom title area and keep top title
     // visible when collapsed.
     SideEffect {
-        if (scrollBehavior?.state?.heightOffsetLimit != pinnedHeightPx - maxHeightPx.value) {
-            scrollBehavior?.state?.heightOffsetLimit = pinnedHeightPx - maxHeightPx.value
+        if (scrollBehavior?.state?.heightOffsetLimit != pinnedHeightPx - maxHeightPx.floatValue) {
+            scrollBehavior?.state?.heightOffsetLimit = pinnedHeightPx - maxHeightPx.floatValue
         }
     }
 
@@ -370,16 +371,20 @@
             )
             TopAppBarLayout(
                 modifier = Modifier.clipToBounds(),
-                heightPx = maxHeightPx.value - pinnedHeightPx +
+                heightPx = maxHeightPx.floatValue - pinnedHeightPx +
                     (scrollBehavior?.state?.heightOffset ?: 0f),
                 navigationIconContentColor = colors.navigationIconContentColor,
                 titleContentColor = colors.titleContentColor,
                 actionIconContentColor = colors.actionIconContentColor,
                 title = {
                     Box(modifier = Modifier.onGloballyPositioned { coordinates ->
-                        density.run {
-                            maxHeightPx.value =
-                                MaxHeightWithoutTitle.toPx() + coordinates.size.height.toFloat()
+                        val measuredMaxHeightPx = density.run {
+                            MaxHeightWithoutTitle.toPx() + coordinates.size.height.toFloat()
+                        }
+                        // Allow larger max height for multi-line title, but do not reduce
+                        // max height to prevent flaky.
+                        if (measuredMaxHeightPx > defaultMaxHeightPx) {
+                            maxHeightPx.floatValue = measuredMaxHeightPx
                         }
                     }) { title() }
                 },
@@ -506,7 +511,7 @@
                 0
             }
 
-        val layoutHeight = heightPx.roundToInt()
+        val layoutHeight = if (heightPx.isNaN()) 0 else heightPx.roundToInt()
 
         layout(constraints.maxWidth, layoutHeight) {
             // Navigation icon
@@ -612,9 +617,9 @@
 // Medium or Large app bar.
 private val TopTitleAlphaEasing = CubicBezierEasing(.8f, 0f, .8f, .15f)
 
-private val MaxHeightWithoutTitle = 124.dp
-private val DefaultTitleHeight = 52.dp
-private val ContainerHeight = 56.dp
+internal val MaxHeightWithoutTitle = 124.dp
+internal val DefaultTitleHeight = 52.dp
+internal val ContainerHeight = 56.dp
 private val LargeTitleBottomPadding = 28.dp
 private val TopAppBarHorizontalPadding = 4.dp
 
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/SearchScaffold.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/SearchScaffold.kt
index 67c4cdc..67f4418 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/SearchScaffold.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/SearchScaffold.kt
@@ -163,7 +163,6 @@
     BackHandler { onClose() }
 }
 
-@OptIn(ExperimentalMaterial3Api::class)
 @Composable
 private fun SearchBox(query: TextFieldValue, onQueryChange: (TextFieldValue) -> Unit) {
     val focusRequester = remember { FocusRequester() }
@@ -186,8 +185,9 @@
         keyboardOptions = KeyboardOptions(imeAction = ImeAction.Search),
         keyboardActions = KeyboardActions(onSearch = { hideKeyboardAction() }),
         singleLine = true,
-        colors = TextFieldDefaults.textFieldColors(
-            containerColor = Color.Transparent,
+        colors = TextFieldDefaults.colors(
+            focusedContainerColor = Color.Transparent,
+            unfocusedContainerColor = Color.Transparent,
             focusedIndicatorColor = Color.Transparent,
             unfocusedIndicatorColor = Color.Transparent,
         ),
diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/scaffold/CustomizedAppBarTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/scaffold/CustomizedAppBarTest.kt
new file mode 100644
index 0000000..a6a5ed22
--- /dev/null
+++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/scaffold/CustomizedAppBarTest.kt
@@ -0,0 +1,457 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.spa.widget.scaffold
+
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.lazy.LazyColumn
+import androidx.compose.foundation.lazy.LazyListState
+import androidx.compose.foundation.lazy.LazyRow
+import androidx.compose.foundation.lazy.rememberLazyListState
+import androidx.compose.material3.CenterAlignedTopAppBar
+import androidx.compose.material3.ExperimentalMaterial3Api
+import androidx.compose.material3.Icon
+import androidx.compose.material3.IconButton
+import androidx.compose.material3.LocalContentColor
+import androidx.compose.material3.LocalTextStyle
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Scaffold
+import androidx.compose.material3.Text
+import androidx.compose.material3.TopAppBarDefaults
+import androidx.compose.material3.TopAppBarScrollBehavior
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.painter.ColorPainter
+import androidx.compose.ui.input.nestedscroll.nestedScroll
+import androidx.compose.ui.platform.LocalDensity
+import androidx.compose.ui.platform.testTag
+import androidx.compose.ui.semantics.semantics
+import androidx.compose.ui.test.assertHeightIsEqualTo
+import androidx.compose.ui.test.assertIsDisplayed
+import androidx.compose.ui.test.assertLeftPositionInRootIsEqualTo
+import androidx.compose.ui.test.assertTopPositionInRootIsEqualTo
+import androidx.compose.ui.test.assertWidthIsEqualTo
+import androidx.compose.ui.test.getUnclippedBoundsInRoot
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.test.onNodeWithTag
+import androidx.compose.ui.test.onNodeWithText
+import androidx.compose.ui.test.performTouchInput
+import androidx.compose.ui.test.swipeLeft
+import androidx.compose.ui.test.swipeRight
+import androidx.compose.ui.text.TextStyle
+import androidx.compose.ui.unit.Dp
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.unit.height
+import androidx.compose.ui.unit.width
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.settingslib.spa.testutils.rootWidth
+import com.android.settingslib.spa.testutils.setContentForSizeAssertions
+import com.google.common.truth.Truth.assertThat
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@OptIn(ExperimentalMaterial3Api::class)
+@RunWith(AndroidJUnit4::class)
+class CustomizedAppBarTest {
+
+    @get:Rule
+    val rule = createComposeRule()
+
+    @Test
+    fun smallTopAppBar_expandsToScreen() {
+        rule
+            .setContentForSizeAssertions {
+                CustomizedTopAppBar(title = { Text("Title") })
+            }
+            .assertHeightIsEqualTo(ContainerHeight)
+            .assertWidthIsEqualTo(rule.rootWidth())
+    }
+
+    @Test
+    fun smallTopAppBar_withTitle() {
+        val title = "Title"
+        rule.setContent {
+            Box(Modifier.testTag(TopAppBarTestTag)) {
+                CustomizedTopAppBar(title = { Text(title) })
+            }
+        }
+        rule.onNodeWithText(title).assertIsDisplayed()
+    }
+
+    @Test
+    fun smallTopAppBar_default_positioning() {
+        rule.setContent {
+            Box(Modifier.testTag(TopAppBarTestTag)) {
+                CustomizedTopAppBar(
+                    navigationIcon = {
+                        FakeIcon(Modifier.testTag(NavigationIconTestTag))
+                    },
+                    title = {
+                        Text("Title", Modifier.testTag(TitleTestTag))
+                    },
+                    actions = {
+                        FakeIcon(Modifier.testTag(ActionsTestTag))
+                    }
+                )
+            }
+        }
+        assertSmallDefaultPositioning()
+    }
+
+    @Test
+    fun smallTopAppBar_noNavigationIcon_positioning() {
+        rule.setContent {
+            Box(Modifier.testTag(TopAppBarTestTag)) {
+                CustomizedTopAppBar(
+                    title = {
+                        Text("Title", Modifier.testTag(TitleTestTag))
+                    },
+                    actions = {
+                        FakeIcon(Modifier.testTag(ActionsTestTag))
+                    }
+                )
+            }
+        }
+        assertSmallPositioningWithoutNavigation()
+    }
+
+    @Test
+    fun smallTopAppBar_titleDefaultStyle() {
+        var textStyle: TextStyle? = null
+        var expectedTextStyle: TextStyle? = null
+        rule.setContent {
+            CustomizedTopAppBar(
+                title = {
+                    Text("Title")
+                    textStyle = LocalTextStyle.current
+                    expectedTextStyle = MaterialTheme.typography.titleMedium
+                },
+            )
+        }
+        assertThat(textStyle).isNotNull()
+        assertThat(textStyle).isEqualTo(expectedTextStyle)
+    }
+
+    @Test
+    fun smallTopAppBar_contentColor() {
+        var titleColor: Color = Color.Unspecified
+        var navigationIconColor: Color = Color.Unspecified
+        var actionsColor: Color = Color.Unspecified
+        var expectedTitleColor: Color = Color.Unspecified
+        var expectedNavigationIconColor: Color = Color.Unspecified
+        var expectedActionsColor: Color = Color.Unspecified
+
+        rule.setContent {
+            CustomizedTopAppBar(
+                navigationIcon = {
+                    FakeIcon(Modifier.testTag(NavigationIconTestTag))
+                    navigationIconColor = LocalContentColor.current
+                    expectedNavigationIconColor =
+                        TopAppBarDefaults.topAppBarColors().navigationIconContentColor
+                    // fraction = 0f to indicate no scroll.
+                },
+                title = {
+                    Text("Title", Modifier.testTag(TitleTestTag))
+                    titleColor = LocalContentColor.current
+                    expectedTitleColor = TopAppBarDefaults.topAppBarColors().titleContentColor
+                },
+                actions = {
+                    FakeIcon(Modifier.testTag(ActionsTestTag))
+                    actionsColor = LocalContentColor.current
+                    expectedActionsColor =
+                        TopAppBarDefaults.topAppBarColors().actionIconContentColor
+                }
+            )
+        }
+        assertThat(navigationIconColor).isNotNull()
+        assertThat(titleColor).isNotNull()
+        assertThat(actionsColor).isNotNull()
+        assertThat(navigationIconColor).isEqualTo(expectedNavigationIconColor)
+        assertThat(titleColor).isEqualTo(expectedTitleColor)
+        assertThat(actionsColor).isEqualTo(expectedActionsColor)
+    }
+
+    @Test
+    fun largeTopAppBar_scrolled_positioning() {
+        val content = @Composable { scrollBehavior: TopAppBarScrollBehavior? ->
+            Box(Modifier.testTag(TopAppBarTestTag)) {
+                CustomizedLargeTopAppBar(
+                    navigationIcon = {
+                        FakeIcon(Modifier.testTag(NavigationIconTestTag))
+                    },
+                    title = "Title",
+                    actions = {
+                        FakeIcon(Modifier.testTag(ActionsTestTag))
+                    },
+                    scrollBehavior = scrollBehavior,
+                )
+            }
+        }
+        assertLargeScrolledHeight(
+            MaxHeightWithoutTitle + DefaultTitleHeight,
+            MaxHeightWithoutTitle + DefaultTitleHeight,
+            content,
+        )
+    }
+
+    @OptIn(ExperimentalMaterial3Api::class)
+    @Test
+    fun topAppBar_enterAlways_allowHorizontalScroll() {
+        lateinit var state: LazyListState
+        rule.setContent {
+            state = rememberLazyListState()
+            MultiPageContent(TopAppBarDefaults.enterAlwaysScrollBehavior(), state)
+        }
+
+        rule.onNodeWithTag(LazyListTag).performTouchInput { swipeLeft() }
+        rule.runOnIdle {
+            assertThat(state.firstVisibleItemIndex).isEqualTo(1)
+        }
+
+        rule.onNodeWithTag(LazyListTag).performTouchInput { swipeRight() }
+        rule.runOnIdle {
+            assertThat(state.firstVisibleItemIndex).isEqualTo(0)
+        }
+    }
+
+    @OptIn(ExperimentalMaterial3Api::class)
+    @Test
+    fun topAppBar_exitUntilCollapsed_allowHorizontalScroll() {
+        lateinit var state: LazyListState
+        rule.setContent {
+            state = rememberLazyListState()
+            MultiPageContent(TopAppBarDefaults.exitUntilCollapsedScrollBehavior(), state)
+        }
+
+        rule.onNodeWithTag(LazyListTag).performTouchInput { swipeLeft() }
+        rule.runOnIdle {
+            assertThat(state.firstVisibleItemIndex).isEqualTo(1)
+        }
+
+        rule.onNodeWithTag(LazyListTag).performTouchInput { swipeRight() }
+        rule.runOnIdle {
+            assertThat(state.firstVisibleItemIndex).isEqualTo(0)
+        }
+    }
+
+    @OptIn(ExperimentalMaterial3Api::class)
+    @Test
+    fun topAppBar_pinned_allowHorizontalScroll() {
+        lateinit var state: LazyListState
+        rule.setContent {
+            state = rememberLazyListState()
+            MultiPageContent(
+                TopAppBarDefaults.pinnedScrollBehavior(),
+                state
+            )
+        }
+
+        rule.onNodeWithTag(LazyListTag).performTouchInput { swipeLeft() }
+        rule.runOnIdle {
+            assertThat(state.firstVisibleItemIndex).isEqualTo(1)
+        }
+
+        rule.onNodeWithTag(LazyListTag).performTouchInput { swipeRight() }
+        rule.runOnIdle {
+            assertThat(state.firstVisibleItemIndex).isEqualTo(0)
+        }
+    }
+
+    @OptIn(ExperimentalMaterial3Api::class)
+    @Composable
+    private fun MultiPageContent(scrollBehavior: TopAppBarScrollBehavior, state: LazyListState) {
+        Scaffold(
+            modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection),
+            topBar = {
+                CustomizedTopAppBar(
+                    title = { Text(text = "Title") },
+                )
+            }
+        ) { contentPadding ->
+            LazyRow(
+                Modifier
+                    .fillMaxSize()
+                    .testTag(LazyListTag), state
+            ) {
+                items(2) { page ->
+                    LazyColumn(
+                        modifier = Modifier.fillParentMaxSize(),
+                        contentPadding = contentPadding
+                    ) {
+                        items(50) {
+                            Text(
+                                modifier = Modifier.fillParentMaxWidth(),
+                                text = "Item #$page x $it"
+                            )
+                        }
+                    }
+                }
+            }
+        }
+    }
+
+    /**
+     * Checks the app bar's components positioning when it's a [CustomizedTopAppBar], a
+     * [CenterAlignedTopAppBar], or a larger app bar that is scrolled up and collapsed into a small
+     * configuration and there is no navigation icon.
+     */
+    private fun assertSmallPositioningWithoutNavigation(isCenteredTitle: Boolean = false) {
+        val appBarBounds = rule.onNodeWithTag(TopAppBarTestTag).getUnclippedBoundsInRoot()
+        val titleBounds = rule.onNodeWithTag(TitleTestTag).getUnclippedBoundsInRoot()
+
+        val titleNode = rule.onNodeWithTag(TitleTestTag)
+        // Title should be vertically centered
+        titleNode.assertTopPositionInRootIsEqualTo((appBarBounds.height - titleBounds.height) / 2)
+        if (isCenteredTitle) {
+            // Title should be horizontally centered
+            titleNode.assertLeftPositionInRootIsEqualTo(
+                (appBarBounds.width - titleBounds.width) / 2
+            )
+        } else {
+            // Title should now be placed 16.dp from the start, as there is no navigation icon
+            // 4.dp padding for the whole app bar + 12.dp inset
+            titleNode.assertLeftPositionInRootIsEqualTo(4.dp + 12.dp)
+        }
+
+        rule.onNodeWithTag(ActionsTestTag)
+            // Action should still be placed at the end
+            .assertLeftPositionInRootIsEqualTo(expectedActionPosition(appBarBounds.width))
+    }
+
+    /**
+     * Checks the app bar's components positioning when it's a [CustomizedTopAppBar] or a
+     * [CenterAlignedTopAppBar].
+     */
+    private fun assertSmallDefaultPositioning(isCenteredTitle: Boolean = false) {
+        val appBarBounds = rule.onNodeWithTag(TopAppBarTestTag).getUnclippedBoundsInRoot()
+        val titleBounds = rule.onNodeWithTag(TitleTestTag).getUnclippedBoundsInRoot()
+        val appBarBottomEdgeY = appBarBounds.top + appBarBounds.height
+
+        rule.onNodeWithTag(NavigationIconTestTag)
+            // Navigation icon should be 4.dp from the start
+            .assertLeftPositionInRootIsEqualTo(AppBarStartAndEndPadding)
+            // Navigation icon should be centered within the height of the app bar.
+            .assertTopPositionInRootIsEqualTo(
+                appBarBottomEdgeY - AppBarTopAndBottomPadding - FakeIconSize
+            )
+
+        val titleNode = rule.onNodeWithTag(TitleTestTag)
+        // Title should be vertically centered
+        titleNode.assertTopPositionInRootIsEqualTo((appBarBounds.height - titleBounds.height) / 2)
+        if (isCenteredTitle) {
+            // Title should be horizontally centered
+            titleNode.assertLeftPositionInRootIsEqualTo(
+                (appBarBounds.width - titleBounds.width) / 2
+            )
+        } else {
+            // Title should be 56.dp from the start
+            // 4.dp padding for the whole app bar + 48.dp icon size + 4.dp title padding.
+            titleNode.assertLeftPositionInRootIsEqualTo(4.dp + FakeIconSize + 4.dp)
+        }
+
+        rule.onNodeWithTag(ActionsTestTag)
+            // Action should be placed at the end
+            .assertLeftPositionInRootIsEqualTo(expectedActionPosition(appBarBounds.width))
+            // Action should be 8.dp from the top
+            .assertTopPositionInRootIsEqualTo(
+                appBarBottomEdgeY - AppBarTopAndBottomPadding - FakeIconSize
+            )
+    }
+
+    /**
+     * Checks that changing values at a [CustomizedLargeTopAppBar] scroll behavior
+     * affects the height of the app bar.
+     *
+     * This check partially and fully collapses the app bar to test its height.
+     *
+     * @param appBarMaxHeight the max height of the app bar [content]
+     * @param appBarMinHeight the min height of the app bar [content]
+     * @param content a Composable that adds a CustomizedLargeTopAppBar
+     */
+    @OptIn(ExperimentalMaterial3Api::class)
+    private fun assertLargeScrolledHeight(
+        appBarMaxHeight: Dp,
+        appBarMinHeight: Dp,
+        content: @Composable (TopAppBarScrollBehavior?) -> Unit
+    ) {
+        val fullyCollapsedOffsetDp = appBarMaxHeight - appBarMinHeight
+        val partiallyCollapsedOffsetDp = fullyCollapsedOffsetDp / 3
+        var partiallyCollapsedHeightOffsetPx = 0f
+        var fullyCollapsedHeightOffsetPx = 0f
+        lateinit var scrollBehavior: TopAppBarScrollBehavior
+        rule.setContent {
+            scrollBehavior = TopAppBarDefaults.exitUntilCollapsedScrollBehavior()
+            with(LocalDensity.current) {
+                partiallyCollapsedHeightOffsetPx = partiallyCollapsedOffsetDp.toPx()
+                fullyCollapsedHeightOffsetPx = fullyCollapsedOffsetDp.toPx()
+            }
+
+            content(scrollBehavior)
+        }
+
+        // Simulate a partially collapsed app bar.
+        rule.runOnIdle {
+            scrollBehavior.state.heightOffset = -partiallyCollapsedHeightOffsetPx
+            scrollBehavior.state.contentOffset = -partiallyCollapsedHeightOffsetPx
+        }
+        rule.waitForIdle()
+        rule.onNodeWithTag(TopAppBarTestTag)
+            .assertHeightIsEqualTo(
+                appBarMaxHeight - partiallyCollapsedOffsetDp
+            )
+
+        // Simulate a fully collapsed app bar.
+        rule.runOnIdle {
+            scrollBehavior.state.heightOffset = -fullyCollapsedHeightOffsetPx
+            // Simulate additional content scroll beyond the max offset scroll.
+            scrollBehavior.state.contentOffset =
+                -fullyCollapsedHeightOffsetPx - partiallyCollapsedHeightOffsetPx
+        }
+        rule.waitForIdle()
+        // Check that the app bar collapsed to its min height.
+        rule.onNodeWithTag(TopAppBarTestTag).assertHeightIsEqualTo(appBarMinHeight)
+    }
+
+    /**
+     * An [IconButton] with an [Icon] inside for testing positions.
+     *
+     * An [IconButton] is defaulted to be 48X48dp, while its child [Icon] is defaulted to 24x24dp.
+     */
+    private val FakeIcon = @Composable { modifier: Modifier ->
+        IconButton(
+            onClick = { /* doSomething() */ },
+            modifier = modifier.semantics(mergeDescendants = true) {}
+        ) {
+            Icon(ColorPainter(Color.Red), null)
+        }
+    }
+
+    private fun expectedActionPosition(appBarWidth: Dp): Dp =
+        appBarWidth - AppBarStartAndEndPadding - FakeIconSize
+
+    private val FakeIconSize = 48.dp
+    private val AppBarStartAndEndPadding = 4.dp
+    private val AppBarTopAndBottomPadding = (ContainerHeight - FakeIconSize) / 2
+
+    private val LazyListTag = "lazyList"
+    private val TopAppBarTestTag = "bar"
+    private val NavigationIconTestTag = "navigationIcon"
+    private val TitleTestTag = "title"
+    private val ActionsTestTag = "actions"
+}
diff --git a/packages/SettingsLib/Spa/testutils/src/com/android/settingslib/spa/testutils/ComposeContentTestRuleExt.kt b/packages/SettingsLib/Spa/testutils/src/com/android/settingslib/spa/testutils/ComposeContentTestRuleExt.kt
index a5d1f40..0436fc2 100644
--- a/packages/SettingsLib/Spa/testutils/src/com/android/settingslib/spa/testutils/ComposeContentTestRuleExt.kt
+++ b/packages/SettingsLib/Spa/testutils/src/com/android/settingslib/spa/testutils/ComposeContentTestRuleExt.kt
@@ -16,13 +16,28 @@
 
 package com.android.settingslib.spa.testutils
 
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.sizeIn
+import androidx.compose.material3.Surface
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.platform.testTag
 import androidx.compose.ui.test.ComposeTimeoutException
 import androidx.compose.ui.test.SemanticsMatcher
 import androidx.compose.ui.test.SemanticsNodeInteraction
+import androidx.compose.ui.test.getUnclippedBoundsInRoot
 import androidx.compose.ui.test.hasAnyAncestor
 import androidx.compose.ui.test.hasText
 import androidx.compose.ui.test.isDialog
 import androidx.compose.ui.test.junit4.ComposeContentTestRule
+import androidx.compose.ui.test.junit4.ComposeTestRule
+import androidx.compose.ui.test.onNodeWithTag
+import androidx.compose.ui.test.onRoot
+import androidx.compose.ui.unit.Dp
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.unit.height
+import androidx.compose.ui.unit.width
+import com.android.settingslib.spa.framework.theme.SettingsTheme
 
 /** Blocks until the found a semantics node that match the given condition. */
 fun ComposeContentTestRule.waitUntilExists(matcher: SemanticsMatcher) = waitUntil {
@@ -39,3 +54,39 @@
 /** Finds a text node that within dialog. */
 fun ComposeContentTestRule.onDialogText(text: String): SemanticsNodeInteraction =
     onNode(hasAnyAncestor(isDialog()) and hasText(text))
+
+fun ComposeTestRule.rootWidth(): Dp = onRoot().getUnclippedBoundsInRoot().width
+
+fun ComposeTestRule.rootHeight(): Dp = onRoot().getUnclippedBoundsInRoot().height
+
+/**
+ * Constant to emulate very big but finite constraints
+ */
+private val sizeAssertionMaxSize = 5000.dp
+
+private const val SIZE_ASSERTION_TAG = "containerForSizeAssertion"
+
+fun ComposeContentTestRule.setContentForSizeAssertions(
+    parentMaxWidth: Dp = sizeAssertionMaxSize,
+    parentMaxHeight: Dp = sizeAssertionMaxSize,
+    // TODO : figure out better way to make it flexible
+    content: @Composable () -> Unit
+): SemanticsNodeInteraction {
+    setContent {
+        SettingsTheme {
+            Surface {
+                Box {
+                    Box(
+                        Modifier
+                            .sizeIn(maxWidth = parentMaxWidth, maxHeight = parentMaxHeight)
+                            .testTag(SIZE_ASSERTION_TAG)
+                    ) {
+                        content()
+                    }
+                }
+            }
+        }
+    }
+
+    return onNodeWithTag(SIZE_ASSERTION_TAG)
+}
diff --git a/packages/SettingsLib/res/layout/dialog_with_icon.xml b/packages/SettingsLib/res/layout/dialog_with_icon.xml
index 2f30508..3586dcb 100644
--- a/packages/SettingsLib/res/layout/dialog_with_icon.xml
+++ b/packages/SettingsLib/res/layout/dialog_with_icon.xml
@@ -33,13 +33,13 @@
             android:importantForAccessibility="no"/>
         <TextView
             android:id="@+id/dialog_with_icon_title"
-            android:layout_width="wrap_content"
+            android:layout_width="match_parent"
             android:layout_height="wrap_content"
             android:gravity="center"
             style="@style/DialogWithIconTitle"/>
         <TextView
             android:id="@+id/dialog_with_icon_message"
-            android:layout_width="wrap_content"
+            android:layout_width="match_parent"
             android:layout_height="wrap_content"
             android:gravity="center"
             style="@style/TextAppearanceSmall"/>
diff --git a/packages/SettingsLib/src/com/android/settingslib/applications/InterestingConfigChanges.java b/packages/SettingsLib/src/com/android/settingslib/applications/InterestingConfigChanges.java
index 64a0781..5d520ce 100644
--- a/packages/SettingsLib/src/com/android/settingslib/applications/InterestingConfigChanges.java
+++ b/packages/SettingsLib/src/com/android/settingslib/applications/InterestingConfigChanges.java
@@ -28,11 +28,11 @@
 public class InterestingConfigChanges {
     private final Configuration mLastConfiguration = new Configuration();
     private final int mFlags;
-    private int mLastDensity;
 
     public InterestingConfigChanges() {
         this(ActivityInfo.CONFIG_LOCALE | ActivityInfo.CONFIG_LAYOUT_DIRECTION
-                | ActivityInfo.CONFIG_UI_MODE | ActivityInfo.CONFIG_ASSETS_PATHS);
+                | ActivityInfo.CONFIG_UI_MODE | ActivityInfo.CONFIG_ASSETS_PATHS
+                | ActivityInfo.CONFIG_DENSITY);
     }
 
     public InterestingConfigChanges(int flags) {
@@ -50,11 +50,6 @@
     public boolean applyNewConfig(Resources res) {
         int configChanges = mLastConfiguration.updateFrom(
                 Configuration.generateDelta(mLastConfiguration, res.getConfiguration()));
-        boolean densityChanged = mLastDensity != res.getDisplayMetrics().densityDpi;
-        if (densityChanged || (configChanges & (mFlags)) != 0) {
-            mLastDensity = res.getDisplayMetrics().densityDpi;
-            return true;
-        }
-        return false;
+        return (configChanges & (mFlags)) != 0;
     }
 }
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java b/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java
index f2abf87..2a486a9 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java
@@ -91,6 +91,7 @@
     MediaRouter2Manager mRouterManager;
     @VisibleForTesting
     String mPackageName;
+    boolean mIsScanning = false;
 
     private MediaDevice mCurrentConnectedDevice;
     private LocalBluetoothManager mBluetoothManager;
@@ -110,22 +111,29 @@
 
     @Override
     public void startScan() {
-        mMediaDevices.clear();
-        mRouterManager.registerCallback(mExecutor, mMediaRouterCallback);
-        mRouterManager.registerScanRequest();
-        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE
-                && !TextUtils.isEmpty(mPackageName)) {
-            RouteListingPreference routeListingPreference =
-                    mRouterManager.getRouteListingPreference(mPackageName);
-            Api34Impl.onRouteListingPreferenceUpdated(routeListingPreference, mPreferenceItemMap);
+        if (!mIsScanning) {
+            mMediaDevices.clear();
+            mRouterManager.registerCallback(mExecutor, mMediaRouterCallback);
+            mRouterManager.registerScanRequest();
+            mIsScanning = true;
+            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE
+                    && !TextUtils.isEmpty(mPackageName)) {
+                RouteListingPreference routeListingPreference =
+                        mRouterManager.getRouteListingPreference(mPackageName);
+                Api34Impl.onRouteListingPreferenceUpdated(routeListingPreference,
+                        mPreferenceItemMap);
+            }
+            refreshDevices();
         }
-        refreshDevices();
     }
 
     @Override
     public void stopScan() {
-        mRouterManager.unregisterCallback(mMediaRouterCallback);
-        mRouterManager.unregisterScanRequest();
+        if (mIsScanning) {
+            mRouterManager.unregisterCallback(mMediaRouterCallback);
+            mRouterManager.unregisterScanRequest();
+            mIsScanning = false;
+        }
     }
 
     /**
@@ -701,20 +709,19 @@
                 List<MediaRoute2Info> selectedRouteInfos, List<MediaRoute2Info> infolist,
                 List<RouteListingPreference.Item> preferenceRouteListing) {
             final List<MediaRoute2Info> sortedInfoList = new ArrayList<>(selectedRouteInfos);
+            infolist.removeAll(selectedRouteInfos);
+            sortedInfoList.addAll(infolist.stream().filter(
+                    MediaRoute2Info::isSystemRoute).collect(Collectors.toList()));
             for (RouteListingPreference.Item item : preferenceRouteListing) {
                 for (MediaRoute2Info info : infolist) {
                     if (item.getRouteId().equals(info.getId())
-                            && !selectedRouteInfos.contains(info)) {
+                            && !selectedRouteInfos.contains(info)
+                            && !info.isSystemRoute()) {
                         sortedInfoList.add(info);
                         break;
                     }
                 }
             }
-            if (sortedInfoList.size() != infolist.size()) {
-                infolist.removeAll(sortedInfoList);
-                sortedInfoList.addAll(infolist.stream().filter(
-                        MediaRoute2Info::isSystemRoute).collect(Collectors.toList()));
-            }
             return sortedInfoList;
         }
 
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InfoMediaManagerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InfoMediaManagerTest.java
index 39780f3..7b8815e 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InfoMediaManagerTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InfoMediaManagerTest.java
@@ -114,6 +114,23 @@
     }
 
     @Test
+    public void stopScan_notStartFirst_notCallsUnregister() {
+        mInfoMediaManager.mRouterManager = mRouterManager;
+        mInfoMediaManager.stopScan();
+
+        verify(mRouterManager, never()).unregisterScanRequest();
+    }
+
+    @Test
+    public void stopScan_startFirst_callsUnregister() {
+        mInfoMediaManager.mRouterManager = mRouterManager;
+        mInfoMediaManager.startScan();
+        mInfoMediaManager.stopScan();
+
+        verify(mRouterManager).unregisterScanRequest();
+    }
+
+    @Test
     public void onRouteAdded_getAvailableRoutes_shouldAddMediaDevice() {
         final List<RoutingSessionInfo> routingSessionInfos = new ArrayList<>();
         final RoutingSessionInfo sessionInfo = mock(RoutingSessionInfo.class);
@@ -327,11 +344,12 @@
                 routeListingPreference);
         mInfoMediaManager.mMediaRouterCallback.onRoutesUpdated();
 
-        assertThat(mInfoMediaManager.mMediaDevices).hasSize(3);
+        assertThat(mInfoMediaManager.mMediaDevices).hasSize(4);
         assertThat(mInfoMediaManager.mMediaDevices.get(0).getId()).isEqualTo(TEST_ID);
-        assertThat(mInfoMediaManager.mMediaDevices.get(1).getId()).isEqualTo(TEST_ID_4);
-        assertThat(mInfoMediaManager.mMediaDevices.get(1).isSuggestedDevice()).isTrue();
-        assertThat(mInfoMediaManager.mMediaDevices.get(2).getId()).isEqualTo(TEST_ID_3);
+        assertThat(mInfoMediaManager.mMediaDevices.get(1).getId()).isEqualTo(TEST_ID_1);
+        assertThat(mInfoMediaManager.mMediaDevices.get(2).getId()).isEqualTo(TEST_ID_4);
+        assertThat(mInfoMediaManager.mMediaDevices.get(2).isSuggestedDevice()).isTrue();
+        assertThat(mInfoMediaManager.mMediaDevices.get(3).getId()).isEqualTo(TEST_ID_3);
     }
 
     @Test
@@ -405,8 +423,13 @@
         when(availableInfo3.getClientPackageName()).thenReturn(packageName);
         availableRoutes.add(availableInfo3);
 
-        when(mRouterManager.getAvailableRoutes(packageName)).thenReturn(
-                availableRoutes);
+        final MediaRoute2Info availableInfo4 = mock(MediaRoute2Info.class);
+        when(availableInfo4.getId()).thenReturn(TEST_ID_1);
+        when(availableInfo4.isSystemRoute()).thenReturn(true);
+        when(availableInfo4.getClientPackageName()).thenReturn(TEST_PACKAGE_NAME);
+        availableRoutes.add(availableInfo4);
+
+        when(mRouterManager.getAvailableRoutes(packageName)).thenReturn(availableRoutes);
 
         return availableRoutes;
     }
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsHelper.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsHelper.java
index 48259e1..9da1ab8 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsHelper.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsHelper.java
@@ -38,6 +38,7 @@
 import android.telephony.TelephonyManager;
 import android.text.TextUtils;
 import android.util.ArraySet;
+import android.util.Log;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.app.LocalePicker;
@@ -238,6 +239,7 @@
             // If we fail to apply the setting, by definition nothing happened
             sendBroadcast = false;
             sendBroadcastSystemUI = false;
+            Log.e(TAG, "Failed to restore setting name: " + name + " + value: " + value, e);
         } finally {
             // If this was an element of interest, send the "we just restored it"
             // broadcast with the historical value now that the new value has
diff --git a/packages/SystemUI/compose/core/src/com/android/compose/animation/Expandable.kt b/packages/SystemUI/compose/core/src/com/android/compose/animation/Expandable.kt
index d4a81f9..ac1ef15 100644
--- a/packages/SystemUI/compose/core/src/com/android/compose/animation/Expandable.kt
+++ b/packages/SystemUI/compose/core/src/com/android/compose/animation/Expandable.kt
@@ -70,8 +70,10 @@
 import androidx.compose.ui.platform.LocalContext
 import androidx.compose.ui.unit.Density
 import androidx.compose.ui.unit.dp
-import androidx.lifecycle.ViewTreeLifecycleOwner
-import androidx.lifecycle.ViewTreeViewModelStoreOwner
+import androidx.lifecycle.findViewTreeLifecycleOwner
+import androidx.lifecycle.findViewTreeViewModelStoreOwner
+import androidx.lifecycle.setViewTreeLifecycleOwner
+import androidx.lifecycle.setViewTreeViewModelStoreOwner
 import com.android.systemui.animation.Expandable
 import com.android.systemui.animation.LaunchAnimator
 import kotlin.math.max
@@ -368,13 +370,10 @@
                     context,
                     overlay,
                 )
-            ViewTreeLifecycleOwner.set(
-                overlayViewGroup,
-                ViewTreeLifecycleOwner.get(composeViewRoot),
-            )
-            ViewTreeViewModelStoreOwner.set(
-                overlayViewGroup,
-                ViewTreeViewModelStoreOwner.get(composeViewRoot),
+
+            overlayViewGroup.setViewTreeLifecycleOwner(composeViewRoot.findViewTreeLifecycleOwner())
+            overlayViewGroup.setViewTreeViewModelStoreOwner(
+                composeViewRoot.findViewTreeViewModelStoreOwner()
             )
             ViewTreeSavedStateRegistryOwner.set(
                 overlayViewGroup,
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/qs/footer/ui/compose/FooterActions.kt b/packages/SystemUI/compose/features/src/com/android/systemui/qs/footer/ui/compose/FooterActions.kt
index 75bf281..13acde2 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/qs/footer/ui/compose/FooterActions.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/qs/footer/ui/compose/FooterActions.kt
@@ -121,8 +121,8 @@
         }
     }
 
-    val backgroundColor = colorAttr(R.attr.underSurfaceColor)
-    val contentColor = LocalAndroidColorScheme.current.deprecated.textColorPrimary
+    val backgroundColor = colorAttr(R.attr.underSurface)
+    val contentColor = LocalAndroidColorScheme.current.onSurface
     val backgroundTopRadius = dimensionResource(R.dimen.qs_corner_radius)
     val backgroundModifier =
         remember(
@@ -268,7 +268,7 @@
     val interactionSource = remember { MutableInteractionSource() }
 
     Expandable(
-        color = colorAttr(R.attr.offStateColor),
+        color = colorAttr(R.attr.shadeInactive),
         shape = CircleShape,
         onClick = onClick,
         interactionSource = interactionSource,
@@ -287,7 +287,7 @@
                     number.toString(),
                     modifier = Modifier.align(Alignment.Center),
                     style = MaterialTheme.typography.bodyLarge,
-                    color = LocalAndroidColorScheme.current.deprecated.textColorPrimary,
+                    color = colorAttr(R.attr.onShadeInactiveVariant),
                     // TODO(b/242040009): This should only use a standard text style instead and
                     // should not override the text size.
                     fontSize = 18.sp,
@@ -305,7 +305,7 @@
 @Composable
 private fun NewChangesDot(modifier: Modifier = Modifier) {
     val contentDescription = stringResource(R.string.fgs_dot_content_description)
-    val color = LocalAndroidColorScheme.current.deprecated.colorAccentTertiary
+    val color = LocalAndroidColorScheme.current.tertiary
 
     Canvas(modifier.size(12.dp).semantics { this.contentDescription = contentDescription }) {
         drawCircle(color)
@@ -323,10 +323,9 @@
 ) {
     Expandable(
         shape = CircleShape,
-        color = colorAttr(R.attr.underSurfaceColor),
-        contentColor = LocalAndroidColorScheme.current.deprecated.textColorSecondary,
-        borderStroke =
-            BorderStroke(1.dp, LocalAndroidColorScheme.current.deprecated.colorBackground),
+        color = colorAttr(R.attr.underSurface),
+        contentColor = LocalAndroidColorScheme.current.onSurfaceVariant,
+        borderStroke = BorderStroke(1.dp, colorAttr(R.attr.onShadeActive)),
         modifier = modifier.padding(horizontal = 4.dp),
         onClick = onClick,
     ) {
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 648ef03..d208404 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
@@ -34,7 +34,7 @@
 import com.android.systemui.animation.TextAnimator
 import com.android.systemui.customization.R
 import com.android.systemui.log.LogBuffer
-import com.android.systemui.log.LogLevel.DEBUG
+import com.android.systemui.log.core.LogLevel.DEBUG
 import java.io.PrintWriter
 import java.util.Calendar
 import java.util.Locale
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ClockRegistry.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ClockRegistry.kt
index 1443465..12f7452 100644
--- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ClockRegistry.kt
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ClockRegistry.kt
@@ -24,11 +24,11 @@
 import android.util.Log
 import androidx.annotation.OpenForTesting
 import com.android.systemui.log.LogBuffer
-import com.android.systemui.log.LogLevel
-import com.android.systemui.log.LogMessage
 import com.android.systemui.log.LogMessageImpl
-import com.android.systemui.log.MessageInitializer
-import com.android.systemui.log.MessagePrinter
+import com.android.systemui.log.core.LogLevel
+import com.android.systemui.log.core.LogMessage
+import com.android.systemui.log.core.MessageInitializer
+import com.android.systemui.log.core.MessagePrinter
 import com.android.systemui.plugins.ClockController
 import com.android.systemui.plugins.ClockId
 import com.android.systemui.plugins.ClockMetadata
diff --git a/packages/SystemUI/log/src/com/android/systemui/log/ConstantStringsLoggerImpl.kt b/packages/SystemUI/log/src/com/android/systemui/log/ConstantStringsLoggerImpl.kt
index 6fc52536..a4f4e13 100644
--- a/packages/SystemUI/log/src/com/android/systemui/log/ConstantStringsLoggerImpl.kt
+++ b/packages/SystemUI/log/src/com/android/systemui/log/ConstantStringsLoggerImpl.kt
@@ -16,6 +16,7 @@
 
 package com.android.systemui.log
 
+import com.android.systemui.log.core.LogLevel
 import com.google.errorprone.annotations.CompileTimeConstant
 
 class ConstantStringsLoggerImpl(val buffer: LogBuffer, val tag: String) : ConstantStringsLogger {
diff --git a/packages/SystemUI/log/src/com/android/systemui/log/LogBuffer.kt b/packages/SystemUI/log/src/com/android/systemui/log/LogBuffer.kt
index 2007e76..8858738 100644
--- a/packages/SystemUI/log/src/com/android/systemui/log/LogBuffer.kt
+++ b/packages/SystemUI/log/src/com/android/systemui/log/LogBuffer.kt
@@ -19,6 +19,10 @@
 import android.os.Trace
 import android.util.Log
 import com.android.systemui.common.buffer.RingBuffer
+import com.android.systemui.log.core.LogLevel
+import com.android.systemui.log.core.LogMessage
+import com.android.systemui.log.core.MessageInitializer
+import com.android.systemui.log.core.MessagePrinter
 import com.google.errorprone.annotations.CompileTimeConstant
 import java.io.PrintWriter
 import java.util.concurrent.ArrayBlockingQueue
@@ -292,11 +296,5 @@
     }
 }
 
-/**
- * A function that will be called immediately to store relevant data on the log message. The value
- * of `this` will be the LogMessage to be initialized.
- */
-typealias MessageInitializer = LogMessage.() -> Unit
-
 private const val TAG = "LogBuffer"
 private val FROZEN_MESSAGE = LogMessageImpl.create()
diff --git a/packages/SystemUI/log/src/com/android/systemui/log/LogMessageImpl.kt b/packages/SystemUI/log/src/com/android/systemui/log/LogMessageImpl.kt
index 5e10f78..33cc199 100644
--- a/packages/SystemUI/log/src/com/android/systemui/log/LogMessageImpl.kt
+++ b/packages/SystemUI/log/src/com/android/systemui/log/LogMessageImpl.kt
@@ -16,6 +16,10 @@
 
 package com.android.systemui.log
 
+import com.android.systemui.log.core.LogLevel
+import com.android.systemui.log.core.LogMessage
+import com.android.systemui.log.core.MessagePrinter
+
 /** Recyclable implementation of [LogMessage]. */
 data class LogMessageImpl(
     override var level: LogLevel,
diff --git a/packages/SystemUI/log/src/com/android/systemui/log/LogcatEchoTracker.kt b/packages/SystemUI/log/src/com/android/systemui/log/LogcatEchoTracker.kt
index 55f3a73..ae717df 100644
--- a/packages/SystemUI/log/src/com/android/systemui/log/LogcatEchoTracker.kt
+++ b/packages/SystemUI/log/src/com/android/systemui/log/LogcatEchoTracker.kt
@@ -16,6 +16,8 @@
 
 package com.android.systemui.log
 
+import com.android.systemui.log.core.LogLevel
+
 /** Keeps track of which [LogBuffer] messages should also appear in logcat. */
 interface LogcatEchoTracker {
     /** Whether [bufferName] should echo messages of [level] or higher to logcat. */
diff --git a/packages/SystemUI/log/src/com/android/systemui/log/LogcatEchoTrackerDebug.kt b/packages/SystemUI/log/src/com/android/systemui/log/LogcatEchoTrackerDebug.kt
index d0ad28f..9ff48ca 100644
--- a/packages/SystemUI/log/src/com/android/systemui/log/LogcatEchoTrackerDebug.kt
+++ b/packages/SystemUI/log/src/com/android/systemui/log/LogcatEchoTrackerDebug.kt
@@ -23,6 +23,7 @@
 import android.os.Looper
 import android.os.Trace
 import android.provider.Settings
+import com.android.systemui.log.core.LogLevel
 
 /**
  * Version of [LogcatEchoTracker] for debuggable builds
diff --git a/packages/SystemUI/log/src/com/android/systemui/log/LogcatEchoTrackerProd.kt b/packages/SystemUI/log/src/com/android/systemui/log/LogcatEchoTrackerProd.kt
index 56966773..044d97f 100644
--- a/packages/SystemUI/log/src/com/android/systemui/log/LogcatEchoTrackerProd.kt
+++ b/packages/SystemUI/log/src/com/android/systemui/log/LogcatEchoTrackerProd.kt
@@ -16,6 +16,8 @@
 
 package com.android.systemui.log
 
+import com.android.systemui.log.core.LogLevel
+
 /** Production version of [LogcatEchoTracker] that isn't configurable. */
 class LogcatEchoTrackerProd : LogcatEchoTracker {
     override val logInBackgroundThread = false
diff --git a/packages/SystemUI/log/src/com/android/systemui/log/LogLevel.kt b/packages/SystemUI/log/src/com/android/systemui/log/core/LogLevel.kt
similarity index 95%
rename from packages/SystemUI/log/src/com/android/systemui/log/LogLevel.kt
rename to packages/SystemUI/log/src/com/android/systemui/log/core/LogLevel.kt
index 7d9647a..d30d8e9 100644
--- a/packages/SystemUI/log/src/com/android/systemui/log/LogLevel.kt
+++ b/packages/SystemUI/log/src/com/android/systemui/log/core/LogLevel.kt
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.systemui.log
+package com.android.systemui.log.core
 
 import android.util.Log
 
diff --git a/packages/SystemUI/log/src/com/android/systemui/log/LogMessage.kt b/packages/SystemUI/log/src/com/android/systemui/log/core/LogMessage.kt
similarity index 92%
rename from packages/SystemUI/log/src/com/android/systemui/log/LogMessage.kt
rename to packages/SystemUI/log/src/com/android/systemui/log/core/LogMessage.kt
index 8c3988b..3bd6473 100644
--- a/packages/SystemUI/log/src/com/android/systemui/log/LogMessage.kt
+++ b/packages/SystemUI/log/src/com/android/systemui/log/core/LogMessage.kt
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.systemui.log
+package com.android.systemui.log.core
 
 import android.icu.text.SimpleDateFormat
 import java.io.PrintWriter
@@ -67,6 +67,12 @@
 }
 
 /**
+ * A function that will be called immediately to store relevant data on the log message. The value
+ * of `this` will be the LogMessage to be initialized.
+ */
+typealias MessageInitializer = LogMessage.() -> Unit
+
+/**
  * A function that will be called if and when the message needs to be dumped to logcat or a bug
  * report. It should read the data stored by the initializer and convert it to a human-readable
  * string. The value of `this` will be the LogMessage to be printed. **IMPORTANT:** The printer
diff --git a/packages/SystemUI/res-keyguard/color/shade_disabled.xml b/packages/SystemUI/res-keyguard/color/shade_disabled.xml
new file mode 100644
index 0000000..241f203
--- /dev/null
+++ b/packages/SystemUI/res-keyguard/color/shade_disabled.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+  ~ Copyright (C) 2023 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~     http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:color="@android:color/system_neutral1_500" android:lStar="4" />
+</selector>
\ No newline at end of file
diff --git a/packages/SystemUI/res-keyguard/layout/footer_actions_number_button.xml b/packages/SystemUI/res-keyguard/layout/footer_actions_number_button.xml
index a7ffe9c..c09607d 100644
--- a/packages/SystemUI/res-keyguard/layout/footer_actions_number_button.xml
+++ b/packages/SystemUI/res-keyguard/layout/footer_actions_number_button.xml
@@ -26,7 +26,7 @@
         android:layout_height="wrap_content"
         android:textAppearance="@style/TextAppearance.QS.SecurityFooter"
         android:layout_gravity="center"
-        android:textColor="?android:attr/textColorPrimary"
+        android:textColor="?attr/onShadeInactiveVariant"
         android:textSize="18sp"/>
     <ImageView
         android:id="@+id/new_dot"
diff --git a/packages/SystemUI/res-keyguard/layout/footer_actions_text_button.xml b/packages/SystemUI/res-keyguard/layout/footer_actions_text_button.xml
index 6fe7d39..1c31f1d 100644
--- a/packages/SystemUI/res-keyguard/layout/footer_actions_text_button.xml
+++ b/packages/SystemUI/res-keyguard/layout/footer_actions_text_button.xml
@@ -33,7 +33,7 @@
         android:layout_marginEnd="12dp"
         android:contentDescription="@null"
         android:src="@drawable/ic_info_outline"
-        android:tint="?android:attr/textColorSecondary" />
+        android:tint="?attr/onSurfaceVariant" />
 
     <TextView
         android:id="@+id/text"
@@ -43,7 +43,7 @@
         android:maxLines="1"
         android:ellipsize="end"
         android:textAppearance="@style/TextAppearance.QS.SecurityFooter"
-        android:textColor="?android:attr/textColorSecondary"/>
+        android:textColor="?attr/onSurfaceVariant"/>
 
     <ImageView
         android:id="@+id/new_dot"
@@ -62,5 +62,5 @@
         android:contentDescription="@null"
         android:src="@*android:drawable/ic_chevron_end"
         android:autoMirrored="true"
-        android:tint="?android:attr/textColorSecondary" />
+        android:tint="?attr/onSurfaceVariant" />
 </com.android.systemui.animation.view.LaunchableLinearLayout>
\ No newline at end of file
diff --git a/packages/SystemUI/res-product/values/strings.xml b/packages/SystemUI/res-product/values/strings.xml
index 13f72af0..42733a2 100644
--- a/packages/SystemUI/res-product/values/strings.xml
+++ b/packages/SystemUI/res-product/values/strings.xml
@@ -122,6 +122,44 @@
        Try again in <xliff:g id="number">%3$d</xliff:g> seconds.
     </string>
 
+    <!-- Title for notification & dialog that the user's phone last shut down because it got too hot. [CHAR LIMIT=40] -->
+    <string name="thermal_shutdown_title" product="default">Phone turned off due to heat</string>
+    <!-- Title for notification & dialog that the user's device last shut down because it got too hot. [CHAR LIMIT=40] -->
+    <string name="thermal_shutdown_title" product="device">Device turned off due to heat</string>
+    <!-- Title for notification & dialog that the user's tablet last shut down because it got too hot. [CHAR LIMIT=40] -->
+    <string name="thermal_shutdown_title" product="tablet">Tablet turned off due to heat</string>
+    <!-- Message body for notification that user's phone last shut down because it got too hot. [CHAR LIMIT=120] -->
+    <string name="thermal_shutdown_message" product="default">Your phone is now running normally.\nTap for more info</string>
+    <!-- Message body for notification that user's device last shut down because it got too hot. [CHAR LIMIT=120] -->
+    <string name="thermal_shutdown_message" product="device">Your device is now running normally.\nTap for more info</string>
+    <!-- Message body for notification that user's tablet last shut down because it got too hot. [CHAR LIMIT=120] -->
+    <string name="thermal_shutdown_message" product="tablet">Your tablet is now running normally.\nTap for more info</string>
+    <!-- Text body for dialog alerting user that their phone last shut down because it got too hot. [CHAR LIMIT=500] -->
+    <string name="thermal_shutdown_dialog_message" product="default">Your phone was too hot, so it turned off to cool down. Your phone is now running normally.\n\nYour phone may get too hot if you:\n\t&#8226; Use resource-intensive apps (such as gaming, video, or navigation apps)\n\t&#8226; Download or upload large files\n\t&#8226; Use your phone in high temperatures</string>
+    <!-- Text body for dialog alerting user that their device last shut down because it got too hot. [CHAR LIMIT=500] -->
+    <string name="thermal_shutdown_dialog_message" product="device">Your device was too hot, so it turned off to cool down. Your device is now running normally.\n\nYour device may get too hot if you:\n\t&#8226; Use resource-intensive apps (such as gaming, video, or navigation apps)\n\t&#8226; Download or upload large files\n\t&#8226; Use your device in high temperatures</string>
+    <!-- Text body for dialog alerting user that their tablet last shut down because it got too hot. [CHAR LIMIT=500] -->
+    <string name="thermal_shutdown_dialog_message" product="tablet">Your tablet was too hot, so it turned off to cool down. Your tablet is now running normally.\n\nYour tablet may get too hot if you:\n\t&#8226; Use resource-intensive apps (such as gaming, video, or navigation apps)\n\t&#8226; Download or upload large files\n\t&#8226; Use your tablet in high temperatures</string>
+
+    <!-- Title for notification (and dialog) that user's phone has reached a certain temperature and may start to slow down in order to cool down. [CHAR LIMIT=30] -->
+    <string name="high_temp_title" product="default">Phone is getting warm</string>
+    <!-- Title for notification (and dialog) that user's phone has reached a certain temperature and may start to slow down in order to cool down. [CHAR LIMIT=30] -->
+    <string name="high_temp_title" product="device">Device is getting warm</string>
+    <!-- Title for notification (and dialog) that user's phone has reached a certain temperature and may start to slow down in order to cool down. [CHAR LIMIT=30] -->
+    <string name="high_temp_title" product="tablet">Tablet is getting warm</string>
+    <!-- Message body for notification that user's phone has reached a certain temperature and may start to slow down in order to cool down. [CHAR LIMIT=120] -->
+    <string name="high_temp_notif_message" product="default">Some features limited while phone cools down.\nTap for more info</string>
+    <!-- Message body for notification that user's device has reached a certain temperature and may start to slow down in order to cool down. [CHAR LIMIT=120] -->
+    <string name="high_temp_notif_message" product="device">Some features limited while device cools down.\nTap for more info</string>
+    <!-- Message body for notification that user's tablet has reached a certain temperature and may start to slow down in order to cool down. [CHAR LIMIT=120] -->
+    <string name="high_temp_notif_message" product="tablet">Some features limited while tablet cools down.\nTap for more info</string>
+    <!-- Text body for dialog alerting user that their phone has reached a certain temperature and may start to slow down in order to cool down. [CHAR LIMIT=350] -->
+    <string name="high_temp_dialog_message" product="default">Your phone will automatically try to cool down. You can still use your phone, but it may run slower.\n\nOnce your phone has cooled down, it will run normally.</string>
+    <!-- Text body for dialog alerting user that their phone has reached a certain temperature and may start to slow down in order to cool down. [CHAR LIMIT=350] -->
+    <string name="high_temp_dialog_message" product="device">Your device will automatically try to cool down. You can still use your device, but it may run slower.\n\nOnce your device has cooled down, it will run normally.</string>
+    <!-- Text body for dialog alerting user that their phone has reached a certain temperature and may start to slow down in order to cool down. [CHAR LIMIT=350] -->
+    <string name="high_temp_dialog_message" product="tablet">Your tablet will automatically try to cool down. You can still use your tablet, but it may run slower.\n\nOnce your tablet has cooled down, it will run normally.</string>
+
     <!-- Content description of the fingerprint icon when the system-provided fingerprint dialog is showing, to locate the sensor (tablet) for accessibility (not shown on the screen). [CHAR LIMIT=NONE]-->
     <string name="security_settings_sfps_enroll_find_sensor_message" product="tablet">The fingerprint sensor is on the power button. It’s the flat button next to the raised volume button on the edge of the tablet.</string>
     <!-- Content description of the fingerprint icon when the system-provided fingerprint dialog is showing, to locate the sensor (device) for accessibility (not shown on the screen). [CHAR LIMIT=NONE]-->
diff --git a/packages/SystemUI/res/drawable/brightness_mirror_background.xml b/packages/SystemUI/res/drawable/brightness_mirror_background.xml
index 2095103..b5c181b 100644
--- a/packages/SystemUI/res/drawable/brightness_mirror_background.xml
+++ b/packages/SystemUI/res/drawable/brightness_mirror_background.xml
@@ -15,6 +15,6 @@
   ~ limitations under the License
   -->
 <shape xmlns:android="http://schemas.android.com/apk/res/android">
-    <solid android:color="?attr/underSurfaceColor" />
+    <solid android:color="?attr/underSurface" />
     <corners android:radius="@dimen/rounded_slider_background_rounded_corner" />
 </shape>
diff --git a/packages/SystemUI/res/drawable/brightness_progress_drawable.xml b/packages/SystemUI/res/drawable/brightness_progress_drawable.xml
index 569ee76..95c7778c 100644
--- a/packages/SystemUI/res/drawable/brightness_progress_drawable.xml
+++ b/packages/SystemUI/res/drawable/brightness_progress_drawable.xml
@@ -24,7 +24,7 @@
             <shape>
                 <size android:height="@dimen/rounded_slider_track_width" />
                 <corners android:radius="@dimen/rounded_slider_track_corner_radius" />
-                <solid android:color="?attr/offStateColor" />
+                <solid android:color="?attr/shadeInactive" />
             </shape>
         </inset>
     </item>
diff --git a/packages/SystemUI/res/drawable/brightness_progress_full_drawable.xml b/packages/SystemUI/res/drawable/brightness_progress_full_drawable.xml
index 4d9188c..2ea90c7 100644
--- a/packages/SystemUI/res/drawable/brightness_progress_full_drawable.xml
+++ b/packages/SystemUI/res/drawable/brightness_progress_full_drawable.xml
@@ -22,7 +22,7 @@
         android:height="@dimen/rounded_slider_height">
         <shape>
             <size android:height="@dimen/rounded_slider_height" />
-            <solid android:color="?priv-android:attr/colorAccentPrimary" />
+            <solid android:color="?attr/shadeActive" />
             <corners android:radius="@dimen/rounded_slider_corner_radius"/>
         </shape>
     </item>
@@ -34,7 +34,7 @@
         android:right="@dimen/rounded_slider_icon_inset">
         <com.android.systemui.util.AlphaTintDrawableWrapper
             android:drawable="@drawable/ic_brightness"
-            android:tint="?android:attr/textColorPrimaryInverse"
+            android:tint="?attr/onShadeActive"
         />
     </item>
 </layer-list>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/fgs_dot.xml b/packages/SystemUI/res/drawable/fgs_dot.xml
index 3669e1d..0881d7c 100644
--- a/packages/SystemUI/res/drawable/fgs_dot.xml
+++ b/packages/SystemUI/res/drawable/fgs_dot.xml
@@ -19,5 +19,5 @@
     android:shape="oval"
     android:width="12dp"
     android:height="12dp">
-    <solid android:color="?androidprv:attr/colorAccentTertiary" />
+    <solid android:color="?attr/tertiary" />
 </shape>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/qs_customizer_background_primary.xml b/packages/SystemUI/res/drawable/qs_customizer_background_primary.xml
index ea0aafd..e138d09 100644
--- a/packages/SystemUI/res/drawable/qs_customizer_background_primary.xml
+++ b/packages/SystemUI/res/drawable/qs_customizer_background_primary.xml
@@ -15,7 +15,7 @@
 -->
 <inset xmlns:android="http://schemas.android.com/apk/res/android">
     <shape>
-        <solid android:color="?attr/underSurfaceColor"/>
+        <solid android:color="?attr/underSurface"/>
         <corners android:radius="?android:attr/dialogCornerRadius" />
     </shape>
 </inset>
diff --git a/packages/SystemUI/res/drawable/qs_customizer_toolbar.xml b/packages/SystemUI/res/drawable/qs_customizer_toolbar.xml
index ef950fe..f1a24aa 100644
--- a/packages/SystemUI/res/drawable/qs_customizer_toolbar.xml
+++ b/packages/SystemUI/res/drawable/qs_customizer_toolbar.xml
@@ -15,6 +15,6 @@
 -->
 <inset xmlns:android="http://schemas.android.com/apk/res/android">
     <shape>
-        <solid android:color="?attr/underSurfaceColor"/>
+        <solid android:color="?attr/underSurface"/>
     </shape>
 </inset>
diff --git a/packages/SystemUI/res/drawable/qs_dialog_btn_filled.xml b/packages/SystemUI/res/drawable/qs_dialog_btn_filled.xml
index 14cb1de9..c4e45bf 100644
--- a/packages/SystemUI/res/drawable/qs_dialog_btn_filled.xml
+++ b/packages/SystemUI/res/drawable/qs_dialog_btn_filled.xml
@@ -28,7 +28,7 @@
         <item>
             <shape android:shape="rectangle">
                 <corners android:radius="?android:attr/buttonCornerRadius"/>
-                <solid android:color="?androidprv:attr/colorAccentPrimary"/>
+                <solid android:color="?androidprv:attr/materialColorPrimary"/>
                 <padding android:left="@dimen/dialog_button_horizontal_padding"
                          android:top="@dimen/dialog_button_vertical_padding"
                          android:right="@dimen/dialog_button_horizontal_padding"
diff --git a/packages/SystemUI/res/drawable/qs_dialog_btn_filled_large.xml b/packages/SystemUI/res/drawable/qs_dialog_btn_filled_large.xml
index 0544b871..1590daa 100644
--- a/packages/SystemUI/res/drawable/qs_dialog_btn_filled_large.xml
+++ b/packages/SystemUI/res/drawable/qs_dialog_btn_filled_large.xml
@@ -26,7 +26,7 @@
     <item>
         <shape android:shape="rectangle">
             <corners android:radius="18dp"/>
-            <solid android:color="?androidprv:attr/colorAccentPrimary"/>
+            <solid android:color="?androidprv:attr/materialColorPrimaryFixed"/>
         </shape>
     </item>
 </ripple>
diff --git a/packages/SystemUI/res/drawable/qs_dialog_btn_outline.xml b/packages/SystemUI/res/drawable/qs_dialog_btn_outline.xml
index a47299d..b0dc652 100644
--- a/packages/SystemUI/res/drawable/qs_dialog_btn_outline.xml
+++ b/packages/SystemUI/res/drawable/qs_dialog_btn_outline.xml
@@ -29,7 +29,7 @@
             <shape android:shape="rectangle">
                 <corners android:radius="?android:attr/buttonCornerRadius"/>
                 <solid android:color="@android:color/transparent"/>
-                <stroke android:color="?androidprv:attr/colorAccentPrimaryVariant"
+                <stroke android:color="?androidprv:attr/materialColorPrimary"
                         android:width="1dp"
                 />
                 <padding android:left="@dimen/dialog_button_horizontal_padding"
diff --git a/packages/SystemUI/res/drawable/qs_footer_action_circle.xml b/packages/SystemUI/res/drawable/qs_footer_action_circle.xml
index c8c36b0..4a5d4af 100644
--- a/packages/SystemUI/res/drawable/qs_footer_action_circle.xml
+++ b/packages/SystemUI/res/drawable/qs_footer_action_circle.xml
@@ -28,7 +28,7 @@
         </item>
         <item>
             <shape android:shape="rectangle">
-                <solid android:color="?attr/offStateColor"/>
+                <solid android:color="?attr/shadeInactive"/>
                 <corners android:radius="@dimen/qs_footer_action_corner_radius"/>
             </shape>
         </item>
diff --git a/packages/SystemUI/res/drawable/qs_footer_action_circle_color.xml b/packages/SystemUI/res/drawable/qs_footer_action_circle_color.xml
index 6a365000..a8c0349 100644
--- a/packages/SystemUI/res/drawable/qs_footer_action_circle_color.xml
+++ b/packages/SystemUI/res/drawable/qs_footer_action_circle_color.xml
@@ -28,7 +28,7 @@
         </item>
         <item>
             <shape android:shape="rectangle">
-                <solid android:color="?android:attr/colorAccent"/>
+                <solid android:color="?attr/shadeActive"/>
                 <corners android:radius="@dimen/qs_footer_action_corner_radius"/>
             </shape>
         </item>
diff --git a/packages/SystemUI/res/drawable/qs_footer_actions_background.xml b/packages/SystemUI/res/drawable/qs_footer_actions_background.xml
index c9517cd9..a7e8762 100644
--- a/packages/SystemUI/res/drawable/qs_footer_actions_background.xml
+++ b/packages/SystemUI/res/drawable/qs_footer_actions_background.xml
@@ -15,7 +15,7 @@
 -->
 <inset xmlns:android="http://schemas.android.com/apk/res/android">
     <shape>
-        <solid android:color="?attr/underSurfaceColor"/>
+        <solid android:color="?attr/underSurface"/>
         <corners android:topLeftRadius="@dimen/qs_corner_radius"
                  android:topRightRadius="@dimen/qs_corner_radius"/>
     </shape>
diff --git a/packages/SystemUI/res/drawable/qs_security_footer_background.xml b/packages/SystemUI/res/drawable/qs_security_footer_background.xml
index 381af50..0b0055b 100644
--- a/packages/SystemUI/res/drawable/qs_security_footer_background.xml
+++ b/packages/SystemUI/res/drawable/qs_security_footer_background.xml
@@ -29,7 +29,7 @@
         <item>
             <shape android:shape="rectangle">
                 <stroke android:width="1dp"
-                        android:color="?android:attr/colorBackground"/>
+                        android:color="?attr/shadeInactive"/>
                 <corners android:radius="@dimen/qs_security_footer_corner_radius"/>
             </shape>
         </item>
diff --git a/packages/SystemUI/res/drawable/stat_sys_connected_display.xml b/packages/SystemUI/res/drawable/stat_sys_connected_display.xml
new file mode 100644
index 0000000..3f3d6f5
--- /dev/null
+++ b/packages/SystemUI/res/drawable/stat_sys_connected_display.xml
@@ -0,0 +1,25 @@
+<!--
+Copyright (C) 2023 The Android Open Source Project
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+
+         http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:fillColor="@android:color/white"
+    android:viewportWidth="960"
+    android:viewportHeight="960">
+    <path
+        android:fillColor="@android:color/white"
+        android:pathData="M320,840L320,760L400,760L400,680L160,680Q127,680 103.5,656.5Q80,633 80,600L80,200Q80,167 103.5,143.5Q127,120 160,120L800,120Q833,120 856.5,143.5Q880,167 880,200L880,600Q880,633 856.5,656.5Q833,680 800,680L560,680L560,760L640,760L640,840L320,840ZM160,600L800,600Q800,600 800,600Q800,600 800,600L800,200Q800,200 800,200Q800,200 800,200L160,200Q160,200 160,200Q160,200 160,200L160,600Q160,600 160,600Q160,600 160,600ZM160,600Q160,600 160,600Q160,600 160,600L160,200Q160,200 160,200Q160,200 160,200L160,200Q160,200 160,200Q160,200 160,200L160,600Q160,600 160,600Q160,600 160,600L160,600Z" />
+</vector>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/alert_dialog_title_systemui.xml b/packages/SystemUI/res/layout/alert_dialog_title_systemui.xml
index 88f13b4..ca7df86 100644
--- a/packages/SystemUI/res/layout/alert_dialog_title_systemui.xml
+++ b/packages/SystemUI/res/layout/alert_dialog_title_systemui.xml
@@ -42,7 +42,7 @@
             android:layout_marginBottom="16dp"
             android:scaleType="fitCenter"
             android:src="@null"
-            android:tint="?androidprv:attr/colorAccentPrimaryVariant"
+            android:tint="?androidprv:attr/materialColorPrimary"
             />
 
         <TextView
diff --git a/packages/SystemUI/res/layout/qs_footer_impl.xml b/packages/SystemUI/res/layout/qs_footer_impl.xml
index 745cfc6..b8f4c0f 100644
--- a/packages/SystemUI/res/layout/qs_footer_impl.xml
+++ b/packages/SystemUI/res/layout/qs_footer_impl.xml
@@ -53,6 +53,7 @@
                 android:layout_width="wrap_content"
                 android:layout_height="match_parent"
                 android:layout_gravity="center_vertical"
+                android:tint="?attr/shadeActive"
                 android:visibility="gone" />
 
             <FrameLayout
@@ -70,7 +71,7 @@
                     android:focusable="true"
                     android:padding="@dimen/qs_footer_icon_padding"
                     android:src="@*android:drawable/ic_mode_edit"
-                    android:tint="?android:attr/textColorPrimary" />
+                    android:tint="?attr/onSurfaceVariant" />
             </FrameLayout>
 
         </LinearLayout>
diff --git a/packages/SystemUI/res/layout/qs_tile_label.xml b/packages/SystemUI/res/layout/qs_tile_label.xml
index c124aea..974cad3 100644
--- a/packages/SystemUI/res/layout/qs_tile_label.xml
+++ b/packages/SystemUI/res/layout/qs_tile_label.xml
@@ -54,6 +54,6 @@
         android:focusable="false"
         android:importantForAccessibility="no"
         android:textAppearance="@style/TextAppearance.QS.TileLabel.Secondary"
-        android:textColor="?android:attr/textColorSecondary"/>
+        android:textColor="?attr/onShadeInactive"/>
 
 </com.android.systemui.qs.tileimpl.IgnorableChildLinearLayout>
diff --git a/packages/SystemUI/res/layout/screen_record_dialog.xml b/packages/SystemUI/res/layout/screen_record_dialog.xml
index bbf3adf..f6ce70d 100644
--- a/packages/SystemUI/res/layout/screen_record_dialog.xml
+++ b/packages/SystemUI/res/layout/screen_record_dialog.xml
@@ -15,6 +15,7 @@
   limitations under the License.
   -->
 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
     android:layout_width="match_parent"
     android:layout_height="wrap_content"
     android:orientation="vertical">
@@ -47,7 +48,7 @@
                 <TextView
                     android:layout_width="wrap_content"
                     android:layout_height="wrap_content"
-                    android:textAppearance="?android:attr/textAppearanceLarge"
+                    android:textAppearance="@style/TextAppearance.Dialog.Title"
                     android:fontFamily="@*android:string/config_headlineFontFamily"
                     android:text="@string/screenrecord_permission_dialog_title"
                     android:layout_marginTop="22dp"
@@ -56,8 +57,7 @@
                     android:layout_width="wrap_content"
                     android:layout_height="wrap_content"
                     android:text="@string/screenrecord_permission_dialog_warning_entire_screen"
-                    android:textAppearance="?android:attr/textAppearanceSmall"
-                    android:textColor="?android:textColorSecondary"
+                    android:textAppearance="@style/TextAppearance.Dialog.Body.Message"
                     android:gravity="center"
                     android:layout_marginBottom="20dp"/>
 
@@ -81,6 +81,7 @@
                         android:minHeight="48dp"
                         android:layout_weight="1"
                         android:popupBackground="@drawable/screenrecord_spinner_background"
+                        android:textColor="?androidprv:attr/materialColorOnSurface"
                         android:dropDownWidth="274dp"
                         android:prompt="@string/screenrecord_audio_label"/>
                     <Switch
@@ -117,7 +118,7 @@
                         android:text="@string/screenrecord_taps_label"
                         android:textAppearance="?android:attr/textAppearanceMedium"
                         android:fontFamily="@*android:string/config_headlineFontFamily"
-                        android:textColor="?android:attr/textColorPrimary"
+                        android:textColor="?androidprv:attr/materialColorOnSurface"
                         android:importantForAccessibility="no"/>
                     <Switch
                         android:layout_width="wrap_content"
diff --git a/packages/SystemUI/res/layout/screen_share_dialog.xml b/packages/SystemUI/res/layout/screen_share_dialog.xml
index ab522a3..9af46c5 100644
--- a/packages/SystemUI/res/layout/screen_share_dialog.xml
+++ b/packages/SystemUI/res/layout/screen_share_dialog.xml
@@ -36,14 +36,13 @@
             android:layout_width="@dimen/screenrecord_logo_size"
             android:layout_height="@dimen/screenrecord_logo_size"
             android:src="@drawable/ic_media_projection_permission"
-            android:tint="?androidprv:attr/colorAccentPrimaryVariant"
+            android:tint="?androidprv:attr/materialColorPrimary"
             android:importantForAccessibility="no"/>
         <TextView
             android:id="@+id/screen_share_dialog_title"
             android:layout_width="wrap_content"
             android:layout_height="wrap_content"
-            android:textAppearance="?android:attr/textAppearanceLarge"
-            android:fontFamily="@*android:string/config_headlineFontFamily"
+            android:textAppearance="@style/TextAppearance.Dialog.Title"
             android:layout_marginTop="@dimen/screenrecord_title_margin_top"
             android:gravity="center"/>
         <Spinner
@@ -64,8 +63,7 @@
             android:layout_width="match_parent"
             android:layout_height="wrap_content"
             android:text="@string/screenrecord_permission_dialog_warning_entire_screen"
-            android:textAppearance="?android:attr/textAppearanceSmall"
-            android:textColor="?android:textColorSecondary"
+            style="@style/TextAppearance.Dialog.Body.Message"
             android:gravity="start"
             android:lineHeight="@dimen/screenrecord_warning_line_height"/>
 
diff --git a/packages/SystemUI/res/values/attrs.xml b/packages/SystemUI/res/values/attrs.xml
index 3a1d1a8..d693631 100644
--- a/packages/SystemUI/res/values/attrs.xml
+++ b/packages/SystemUI/res/values/attrs.xml
@@ -118,8 +118,25 @@
     <attr name="wallpaperTextColorSecondary" format="reference|color" />
     <attr name="wallpaperTextColorAccent" format="reference|color" />
     <attr name="backgroundProtectedStyle" format="reference" />
-    <attr name="offStateColor" format="reference|color" />
-    <attr name="underSurfaceColor" format="reference|color" />
+
+    <!-- color attribute tokens for QS -->
+    <attr name="isQsTheme" format="boolean" />
+    <attr name="underSurface" format="reference|color"/>
+    <attr name="shadeActive" format="reference|color" />
+    <attr name="onShadeActive" format="reference|color" />
+    <attr name="onShadeActiveVariant" format="reference|color" />
+    <attr name="shadeInactive" format="reference|color" />
+    <attr name="onShadeInactive" format="reference|color" />
+    <attr name="onShadeInactiveVariant" format="reference|color" />
+    <attr name="shadeDisabled" format="reference|color" />
+    <attr name="surfaceBright" format="reference|color" />
+    <attr name="scHigh" format="reference|color" />
+    <attr name="tertiary" format="reference|color" />
+    <attr name="onSurface" format="reference|color" />
+    <attr name="onSurfaceVariant" format="reference|color" />
+    <attr name="outline" format="reference|color" />
+    <attr name="primary" format="reference|color" />
+
 
     <declare-styleable name="SmartReplyView">
         <attr name="spacing" format="dimension" />
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 25f3d22..a36f318 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -2089,23 +2089,11 @@
     <!-- Tuner string -->
     <!-- Tuner string -->
 
-    <!-- Title for notification & dialog that the user's phone last shut down because it got too hot. [CHAR LIMIT=40] -->
-    <string name="thermal_shutdown_title">Phone turned off due to heat</string>
-    <!-- Message body for notification that user's phone last shut down because it got too hot. [CHAR LIMIT=120] -->
-    <string name="thermal_shutdown_message">Your phone is now running normally.\nTap for more info</string>
-    <!-- Text body for dialog alerting user that their phone last shut down because it got too hot. [CHAR LIMIT=500] -->
-    <string name="thermal_shutdown_dialog_message">Your phone was too hot, so it turned off to cool down. Your phone is now running normally.\n\nYour phone may get too hot if you:\n\t&#8226; Use resource-intensive apps (such as gaming, video, or navigation apps)\n\t&#8226; Download or upload large files\n\t&#8226; Use your phone in high temperatures</string>
     <!-- Text help link for care instructions for overheating devices [CHAR LIMIT=40] -->
     <string name="thermal_shutdown_dialog_help_text">See care steps</string>
     <!-- URL for care instructions for overheating devices -->
     <string name="thermal_shutdown_dialog_help_url" translatable="false"></string>
 
-    <!-- Title for notification (and dialog) that user's phone has reached a certain temperature and may start to slow down in order to cool down. [CHAR LIMIT=30] -->
-    <string name="high_temp_title">Phone is getting warm</string>
-    <!-- Message body for notification that user's phone has reached a certain temperature and may start to slow down in order to cool down. [CHAR LIMIT=120] -->
-    <string name="high_temp_notif_message">Some features limited while phone cools down.\nTap for more info</string>
-    <!-- Text body for dialog alerting user that their phone has reached a certain temperature and may start to slow down in order to cool down. [CHAR LIMIT=350] -->
-    <string name="high_temp_dialog_message">Your phone will automatically try to cool down. You can still use your phone, but it may run slower.\n\nOnce your phone has cooled down, it will run normally.</string>
     <!-- Text help link for care instructions for overheating devices [CHAR LIMIT=40] -->
     <string name="high_temp_dialog_help_text">See care steps</string>
     <!-- URL for care instructions for overheating devices -->
diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml
index cb5342a..fd74c7e 100644
--- a/packages/SystemUI/res/values/styles.xml
+++ b/packages/SystemUI/res/values/styles.xml
@@ -83,7 +83,7 @@
 
     <style name="TextAppearance.QS">
         <item name="android:textStyle">normal</item>
-        <item name="android:textColor">?android:attr/textColorPrimary</item>
+        <item name="android:textColor">?attr/onShadeInactive</item>
         <item name="android:fontFamily">@*android:string/config_bodyFontFamily</item>
     </style>
 
@@ -93,7 +93,7 @@
 
     <style name="TextAppearance.QS.DetailItemSecondary">
         <item name="android:textSize">@dimen/qs_tile_text_size</item>
-        <item name="android:textColor">?android:attr/colorAccent</item>
+        <item name="android:textColor">?attr/shadeActive</item>
     </style>
 
     <style name="TextAppearance.QS.Introduction">
@@ -117,11 +117,11 @@
 
     <style name="TextAppearance.QS.DataUsage.Usage">
         <item name="android:textSize">@dimen/qs_data_usage_usage_text_size</item>
-        <item name="android:textColor">?android:attr/colorAccent</item>
+        <item name="android:textColor">?attr/shadeActive</item>
     </style>
 
     <style name="TextAppearance.QS.DataUsage.Secondary">
-        <item name="android:textColor">?android:attr/textColorSecondary</item>
+        <item name="android:textColor">?attr/onShadeInactiveVariant</item>
     </style>
 
     <style name="TextAppearance.QS.TileLabel">
@@ -137,31 +137,31 @@
 
     <style name="TextAppearance.QS.UserSwitcher">
         <item name="android:textSize">@dimen/qs_tile_text_size</item>
-        <item name="android:textColor">?android:attr/textColorSecondary</item>
+        <item name="android:textColor">?androidprv:attr/materialColorOnSurfaceVariant</item>
     </style>
 
     <!-- This is hard coded to be sans-serif-condensed to match the icons -->
 
     <style name="TextAppearance.QS.Status">
         <item name="android:fontFamily">@*android:string/config_headlineFontFamily</item>
-        <item name="android:textColor">?android:attr/textColorPrimary</item>
+        <item name="android:textColor">?attr/onSurface</item>
         <item name="android:textSize">14sp</item>
         <item name="android:letterSpacing">0.01</item>
     </style>
 
     <style name="TextAppearance.QS.SecurityFooter" parent="@style/TextAppearance.QS.Status">
         <item name="android:fontFamily">@*android:string/config_bodyFontFamily</item>
-        <item name="android:textColor">?android:attr/textColorSecondary</item>
+        <item name="android:textColor">?attr/onSurface</item>
     </style>
 
     <style name="TextAppearance.QS.Status.Carriers" />
 
     <style name="TextAppearance.QS.Status.Carriers.NoCarrierText">
-        <item name="android:textColor">?android:attr/textColorSecondary</item>
+        <item name="android:textColor">?attr/onSurfaceVariant</item>
     </style>
 
     <style name="TextAppearance.QS.Status.Build">
-        <item name="android:textColor">?android:attr/textColorSecondary</item>
+        <item name="android:textColor">?attr/onSurfaceVariant</item>
     </style>
 
     <style name="TextAppearance.DeviceManagementDialog.Title" parent="@android:style/TextAppearance.DeviceDefault.DialogWindowTitle"/>
@@ -278,10 +278,10 @@
 
     <style name="DeviceManagementDialogTitle">
         <item name="android:gravity">center</item>
-        <item name="android:textAppearance">@style/TextAppearance.DeviceManagementDialog.Title</item>
+        <item name="android:textAppearance">@style/TextAppearance.Dialog.Title</item>
     </style>
 
-    <style name="TextAppearance.DeviceManagementDialog.Content" parent="@*android:style/TextAppearance.DeviceDefault.Subhead"/>
+    <style name="TextAppearance.DeviceManagementDialog.Content" parent="@style/TextAppearance.Dialog.Body.Message"/>
 
     <style name="BaseBrightnessDialogContainer" parent="@style/Theme.SystemUI">
         <item name="android:layout_width">match_parent</item>
@@ -371,14 +371,30 @@
     </style>
 
     <style name="Theme.SystemUI.QuickSettings" parent="@*android:style/Theme.DeviceDefault">
+        <item name="isQsTheme">true</item>
         <item name="lightIconTheme">@style/QSIconTheme</item>
         <item name="darkIconTheme">@style/QSIconTheme</item>
         <item name="android:colorError">@*android:color/error_color_material_dark</item>
         <item name="android:windowIsFloating">true</item>
         <item name="android:homeAsUpIndicator">@drawable/ic_arrow_back</item>
-        <item name="offStateColor">@color/material_dynamic_neutral20</item>
-        <item name="underSurfaceColor">@color/material_dynamic_neutral0</item>
-        <item name="android:colorBackground">@color/material_dynamic_neutral10</item>
+
+        <item name="surfaceBright">?androidprv:attr/materialColorSurfaceBright</item>
+        <item name="android:colorBackground">?attr/surfaceBright</item>
+        <item name="scHigh">?androidprv:attr/materialColorSurfaceContainerHigh</item>
+        <item name="primary">?androidprv:attr/materialColorPrimary</item>
+        <item name="tertiary">?androidprv:attr/materialColorTertiary</item>
+        <item name="onSurface">?androidprv:attr/materialColorOnSurface</item>
+        <item name="onSurfaceVariant">?androidprv:attr/materialColorOnSurfaceVariant</item>
+        <item name="outline">?androidprv:attr/materialColorOutline</item>
+
+        <item name="shadeActive">@color/material_dynamic_primary90</item>
+        <item name="onShadeActive">@color/material_dynamic_primary10</item>
+        <item name="onShadeActiveVariant">@color/material_dynamic_primary30</item>
+        <item name="shadeInactive">@color/material_dynamic_neutral20</item>
+        <item name="onShadeInactive">@color/material_dynamic_neutral90</item>
+        <item name="onShadeInactiveVariant">@color/material_dynamic_neutral_variant80</item>
+        <item name="shadeDisabled">@color/shade_disabled</item>
+        <item name="underSurface">@color/material_dynamic_neutral0</item>
         <item name="android:itemTextAppearance">@style/Control.MenuItem</item>
     </style>
 
@@ -390,8 +406,15 @@
         <item name="android:windowBackground">@android:color/transparent</item>
     </style>
 
-    <style name="Theme.SystemUI.QuickSettings.Dialog" parent="@android:style/Theme.DeviceDefault.Dialog">
+    <style name="Theme.SystemUI.QuickSettings.Dialog" parent="@style/Theme.SystemUI.Dialog.QuickSettings">
+    </style>
+
+    <!-- Parent style overrides style in the dot inheritance -->
+    <style name="Theme.SystemUI.Dialog.QuickSettings" parent="@style/Theme.SystemUI.QuickSettings">
         <item name="android:dialogCornerRadius">@dimen/notification_corner_radius</item>
+        <item name="android:buttonBarPositiveButtonStyle">@style/Widget.Dialog.Button.QuickSettings</item>
+        <item name="android:buttonBarNegativeButtonStyle">@style/Widget.Dialog.Button.QuickSettings</item>
+        <item name="android:buttonBarNeutralButtonStyle">@style/Widget.Dialog.Button.QuickSettings</item>
     </style>
 
     <!-- Overridden by values-television/styles.xml with tv-specific settings -->
@@ -406,7 +429,7 @@
         <item name="android:buttonBarPositiveButtonStyle">@style/Widget.Dialog.Button</item>
         <item name="android:buttonBarNegativeButtonStyle">@style/Widget.Dialog.Button.BorderButton</item>
         <item name="android:buttonBarNeutralButtonStyle">@style/Widget.Dialog.Button.BorderButton</item>
-        <item name="android:colorBackground">?androidprv:attr/colorSurface</item>
+        <item name="android:colorBackground">?androidprv:attr/materialColorSurfaceBright</item>
         <item name="android:alertDialogStyle">@style/ScrollableAlertDialogStyle</item>
         <item name="android:buttonBarStyle">@style/ButtonBarStyle</item>
         <item name="android:buttonBarButtonStyle">@style/Widget.Dialog.Button.Large</item>
@@ -605,11 +628,11 @@
         <item name="android:letterSpacing">0.01</item>
         <item name="android:lineHeight">20sp</item>
         <item name="android:fontFamily">@*android:string/config_headlineFontFamily</item>
-        <item name="android:textColor">?android:attr/textColorSecondary</item>
+        <item name="android:textColor">?attr/onSurfaceVariant</item>
     </style>
 
     <style name="QSCustomizeToolbar" parent="@*android:style/Widget.DeviceDefault.Toolbar">
-        <item name="android:textColor">?android:attr/textColorPrimary</item>
+        <item name="android:textColor">?attr/onSurface</item>
         <item name="android:elevation">10dp</item>
     </style>
 
@@ -1055,7 +1078,7 @@
     </style>
 
     <style name="TextAppearance.Dialog.Title" parent="@android:style/TextAppearance.DeviceDefault.Large">
-        <item name="android:textColor">?android:attr/textColorPrimary</item>
+        <item name="android:textColor">?androidprv:attr/materialColorOnSurface</item>
         <item name="android:textSize">@dimen/dialog_title_text_size</item>
         <item name="android:fontFamily">@*android:string/config_headlineFontFamily</item>
         <item name="android:lineHeight">32sp</item>
@@ -1064,7 +1087,7 @@
     </style>
 
     <style name="TextAppearance.Dialog.Body" parent="@android:style/TextAppearance.DeviceDefault.Medium">
-        <item name="android:textColor">?android:attr/textColorSecondary</item>
+        <item name="android:textColor">?androidprv:attr/materialColorOnSurfaceVariant</item>
         <item name="android:textSize">14sp</item>
         <item name="android:fontFamily">@*android:string/config_headlineFontFamily</item>
         <item name="android:lineHeight">20sp</item>
@@ -1092,7 +1115,7 @@
     <style name="Widget.Dialog.Button">
         <item name="android:buttonCornerRadius">28dp</item>
         <item name="android:background">@drawable/qs_dialog_btn_filled</item>
-        <item name="android:textColor">?androidprv:attr/textColorOnAccent</item>
+        <item name="android:textColor">?androidprv:attr/materialColorOnPrimary</item>
         <item name="android:textSize">14sp</item>
         <item name="android:lineHeight">20sp</item>
         <item name="android:fontFamily">@*android:string/config_bodyFontFamilyMedium</item>
@@ -1102,12 +1125,18 @@
 
     <style name="Widget.Dialog.Button.BorderButton">
         <item name="android:background">@drawable/qs_dialog_btn_outline</item>
-        <item name="android:textColor">?android:attr/textColorPrimary</item>
+        <item name="android:textColor">?androidprv:attr/materialColorOnSurface</item>
     </style>
 
     <style name="Widget.Dialog.Button.Large">
         <item name="android:background">@drawable/qs_dialog_btn_filled_large</item>
         <item name="android:minHeight">56dp</item>
+        <item name="android:textColor">?androidprv:attr/materialColorOnPrimaryFixed</item>
+    </style>
+
+    <style name="Widget.Dialog.Button.QuickSettings">
+        <item name="android:textColor">?attr/primary</item>
+        <item name="android:background">?android:attr/selectableItemBackground</item>
     </style>
 
     <style name="MainSwitch.Settingslib" parent="@android:style/Theme.DeviceDefault">
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/utilities/Utilities.java b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/utilities/Utilities.java
index 2e6c485..751a3f8 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/utilities/Utilities.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/utilities/Utilities.java
@@ -104,8 +104,7 @@
      * @return updated set of flags from InputMethodService based off {@param oldHints}
      *          Leaves original hints unmodified
      */
-    public static int calculateBackDispositionHints(int oldHints,
-            @InputMethodService.BackDispositionMode int backDisposition,
+    public static int calculateBackDispositionHints(int oldHints, int backDisposition,
             boolean imeShown, boolean showImeSwitcher) {
         int hints = oldHints;
         switch (backDisposition) {
diff --git a/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt b/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt
index 84a2c25..91937af 100644
--- a/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt
+++ b/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt
@@ -42,7 +42,7 @@
 import com.android.systemui.keyguard.shared.model.TransitionState
 import com.android.systemui.lifecycle.repeatWhenAttached
 import com.android.systemui.log.LogBuffer
-import com.android.systemui.log.LogLevel.DEBUG
+import com.android.systemui.log.core.LogLevel.DEBUG
 import com.android.systemui.log.dagger.KeyguardLargeClockLog
 import com.android.systemui.log.dagger.KeyguardSmallClockLog
 import com.android.systemui.plugins.ClockController
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java
index 360f755..4793b4f 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java
@@ -29,7 +29,7 @@
 import com.android.keyguard.dagger.KeyguardStatusViewScope;
 import com.android.systemui.R;
 import com.android.systemui.log.LogBuffer;
-import com.android.systemui.log.LogLevel;
+import com.android.systemui.log.core.LogLevel;
 import com.android.systemui.plugins.ClockController;
 import com.android.systemui.shared.clocks.DefaultClockController;
 
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
index 62a2f90..dba1246 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
@@ -40,7 +40,7 @@
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.keyguard.KeyguardUnlockAnimationController;
 import com.android.systemui.log.LogBuffer;
-import com.android.systemui.log.LogLevel;
+import com.android.systemui.log.core.LogLevel;
 import com.android.systemui.log.dagger.KeyguardClockLog;
 import com.android.systemui.plugins.ClockController;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPinViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPinViewController.java
index 3e16d55..6f59684 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPinViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPinViewController.java
@@ -43,6 +43,15 @@
     private long mPinLength;
 
     private boolean mDisabledAutoConfirmation;
+    /**
+     * Responsible for identifying if PIN hinting is to be enabled or not
+     */
+    private boolean mIsPinHinting;
+
+    /**
+     * Responsible for identifying if auto confirm is enabled or not in Settings
+     */
+    private boolean mIsAutoPinConfirmEnabledInSettings;
 
     protected KeyguardPinViewController(KeyguardPINView view,
             KeyguardUpdateMonitor keyguardUpdateMonitor,
@@ -63,6 +72,9 @@
         mFeatureFlags = featureFlags;
         mBackspaceKey = view.findViewById(R.id.delete_button);
         mPinLength = mLockPatternUtils.getPinLength(KeyguardUpdateMonitor.getCurrentUser());
+        mIsPinHinting = mPinLength == DEFAULT_PIN_LENGTH;
+        mIsAutoPinConfirmEnabledInSettings = mLockPatternUtils.isAutoPinConfirmEnabled(
+                KeyguardUpdateMonitor.getCurrentUser());
     }
 
     @Override
@@ -82,7 +94,7 @@
 
     protected void onUserInput() {
         super.onUserInput();
-        if (isAutoPinConfirmEnabledInSettings()) {
+        if (mIsAutoPinConfirmEnabledInSettings) {
             updateAutoConfirmationState();
             if (mPasswordEntry.getText().length() == mPinLength
                     && mOkButton.getVisibility() == View.INVISIBLE) {
@@ -130,7 +142,7 @@
      * Updates the visibility of the OK button for auto confirm feature
      */
     private void updateOKButtonVisibility() {
-        if (isAutoPinConfirmEnabledInSettings() && !mDisabledAutoConfirmation) {
+        if (mIsPinHinting && !mDisabledAutoConfirmation) {
             mOkButton.setVisibility(View.INVISIBLE);
         } else {
             mOkButton.setVisibility(View.VISIBLE);
@@ -142,10 +154,9 @@
      * Visibility changes are only for auto confirmation configuration.
      */
     private void updateBackSpaceVisibility() {
-        boolean isAutoConfirmation = isAutoPinConfirmEnabledInSettings();
         mBackspaceKey.setTransparentMode(/* isTransparentMode= */
-                isAutoConfirmation && !mDisabledAutoConfirmation);
-        if (isAutoConfirmation) {
+                mIsAutoPinConfirmEnabledInSettings && !mDisabledAutoConfirmation);
+        if (mIsAutoPinConfirmEnabledInSettings) {
             if (mPasswordEntry.getText().length() > 0
                     || mDisabledAutoConfirmation) {
                 mBackspaceKey.setVisibility(View.VISIBLE);
@@ -155,24 +166,8 @@
         }
     }
     /** Updates whether to use pin hinting or not. */
-    void updatePinHinting() {
-        mPasswordEntry.setIsPinHinting(isAutoPinConfirmEnabledInSettings() && isPinHinting()
+    private void updatePinHinting() {
+        mPasswordEntry.setIsPinHinting(mIsAutoPinConfirmEnabledInSettings && mIsPinHinting
                 && !mDisabledAutoConfirmation);
     }
-
-    /**
-     * Responsible for identifying if PIN hinting is to be enabled or not
-     */
-    private boolean isPinHinting() {
-        return mLockPatternUtils.getPinLength(KeyguardUpdateMonitor.getCurrentUser())
-                == DEFAULT_PIN_LENGTH;
-    }
-
-    /**
-     * Responsible for identifying if auto confirm is enabled or not in Settings
-     */
-    private boolean isAutoPinConfirmEnabledInSettings() {
-        //Checks if user has enabled the auto confirm in Settings
-        return mLockPatternUtils.isAutoPinConfirmEnabled(KeyguardUpdateMonitor.getCurrentUser());
-    }
 }
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
index 841b5b3..6853f81 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
@@ -281,6 +281,8 @@
 
     public interface SwipeListener {
         void onSwipeUp();
+        /** */
+        void onSwipeDown();
     }
 
     @VisibleForTesting
@@ -543,6 +545,11 @@
                 if (mSwipeListener != null) {
                     mSwipeListener.onSwipeUp();
                 }
+            } else if (getTranslationY() > TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
+                    MIN_DRAG_SIZE, getResources().getDisplayMetrics())) {
+                if (mSwipeListener != null) {
+                    mSwipeListener.onSwipeDown();
+                }
             }
         }
         return true;
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
index 7c511a3..880f242 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
@@ -70,11 +70,11 @@
 import com.android.systemui.R;
 import com.android.systemui.biometrics.SideFpsController;
 import com.android.systemui.biometrics.SideFpsUiRequestSource;
+import com.android.systemui.bouncer.domain.interactor.BouncerMessageInteractor;
 import com.android.systemui.classifier.FalsingA11yDelegate;
 import com.android.systemui.classifier.FalsingCollector;
 import com.android.systemui.flags.FeatureFlags;
 import com.android.systemui.flags.Flags;
-import com.android.systemui.bouncer.domain.interactor.BouncerMessageInteractor;
 import com.android.systemui.keyguard.domain.interactor.KeyguardFaceAuthInteractor;
 import com.android.systemui.log.SessionTracker;
 import com.android.systemui.plugins.ActivityStarter;
@@ -319,6 +319,11 @@
                         "swipeUpOnBouncer");
             }
         }
+
+        @Override
+        public void onSwipeDown() {
+            mViewMediatorCallback.onBouncerSwipeDown();
+        }
     };
     private final ConfigurationController.ConfigurationListener mConfigurationListener =
             new ConfigurationController.ConfigurationListener() {
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
index 89ef749..c4ea45d 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
@@ -24,6 +24,8 @@
 import static android.content.Intent.ACTION_USER_REMOVED;
 import static android.content.Intent.ACTION_USER_STOPPED;
 import static android.content.Intent.ACTION_USER_UNLOCKED;
+import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FACE;
+import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FINGERPRINT;
 import static android.hardware.biometrics.BiometricConstants.BIOMETRIC_LOCKOUT_NONE;
 import static android.hardware.biometrics.BiometricConstants.BIOMETRIC_LOCKOUT_PERMANENT;
 import static android.hardware.biometrics.BiometricConstants.BIOMETRIC_LOCKOUT_TIMED;
@@ -251,6 +253,7 @@
     private static final int MSG_REQUIRE_NFC_UNLOCK = 345;
     private static final int MSG_KEYGUARD_DISMISS_ANIMATION_FINISHED = 346;
     private static final int MSG_SERVICE_PROVIDERS_UPDATED = 347;
+    private static final int MSG_BIOMETRIC_ENROLLMENT_STATE_CHANGED = 348;
 
     /** Biometric authentication state: Not listening. */
     private static final int BIOMETRIC_STATE_STOPPED = 0;
@@ -2484,6 +2487,9 @@
                     case MSG_KEYGUARD_DISMISS_ANIMATION_FINISHED:
                         handleKeyguardDismissAnimationFinished();
                         break;
+                    case MSG_BIOMETRIC_ENROLLMENT_STATE_CHANGED:
+                        notifyAboutEnrollmentChange(msg.arg1);
+                        break;
                     default:
                         super.handleMessage(msg);
                         break;
@@ -2584,6 +2590,8 @@
 
             @Override
             public void onEnrollmentsChanged(@BiometricAuthenticator.Modality int modality) {
+                mHandler.obtainMessage(MSG_BIOMETRIC_ENROLLMENT_STATE_CHANGED, modality, 0)
+                        .sendToTarget();
                 mainExecutor.execute(() -> updateBiometricListeningState(BIOMETRIC_ACTION_UPDATE,
                         FACE_AUTH_TRIGGERED_ENROLLMENTS_CHANGED));
             }
@@ -3433,6 +3441,25 @@
         return mIsFaceEnrolled;
     }
 
+    private void notifyAboutEnrollmentChange(@BiometricAuthenticator.Modality int modality) {
+        BiometricSourceType biometricSourceType;
+        if (modality == TYPE_FINGERPRINT) {
+            biometricSourceType = FINGERPRINT;
+        } else if (modality == TYPE_FACE) {
+            biometricSourceType = FACE;
+        } else {
+            return;
+        }
+        mLogger.notifyAboutEnrollmentsChanged(biometricSourceType);
+        Assert.isMainThread();
+        for (int i = 0; i < mCallbacks.size(); i++) {
+            KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
+            if (cb != null) {
+                cb.onBiometricEnrollmentStateChanged(biometricSourceType);
+            }
+        }
+    }
+
     private void stopListeningForFingerprint() {
         mLogger.v("stopListeningForFingerprint()");
         if (mFingerprintRunningState == BIOMETRIC_STATE_RUNNING) {
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java
index 7394005..7b59632 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java
@@ -327,4 +327,9 @@
      * Called when the enabled trust agents associated with the specified user.
      */
     public void onEnabledTrustAgentsChanged(int userId) { }
+
+    /**
+     * On biometric enrollment state changed
+     */
+    public void onBiometricEnrollmentStateChanged(BiometricSourceType biometricSourceType) { }
 }
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardVisibilityHelper.java b/packages/SystemUI/src/com/android/keyguard/KeyguardVisibilityHelper.java
index 61af722..71f78c3 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardVisibilityHelper.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardVisibilityHelper.java
@@ -23,7 +23,7 @@
 
 import com.android.app.animation.Interpolators;
 import com.android.systemui.log.LogBuffer;
-import com.android.systemui.log.LogLevel;
+import com.android.systemui.log.core.LogLevel;
 import com.android.systemui.statusbar.StatusBarState;
 import com.android.systemui.statusbar.notification.AnimatableProperty;
 import com.android.systemui.statusbar.notification.PropertyAnimator;
diff --git a/packages/SystemUI/src/com/android/keyguard/ViewMediatorCallback.java b/packages/SystemUI/src/com/android/keyguard/ViewMediatorCallback.java
index 50f8f7e..14ec27a 100644
--- a/packages/SystemUI/src/com/android/keyguard/ViewMediatorCallback.java
+++ b/packages/SystemUI/src/com/android/keyguard/ViewMediatorCallback.java
@@ -104,4 +104,9 @@
      * Call when cancel button is pressed in bouncer.
      */
     void onCancelClicked();
+
+    /**
+     * Determines if bouncer has swiped down.
+     */
+    void onBouncerSwipeDown();
 }
diff --git a/packages/SystemUI/src/com/android/keyguard/logging/BiometricMessageDeferralLogger.kt b/packages/SystemUI/src/com/android/keyguard/logging/BiometricMessageDeferralLogger.kt
index e7295ef..54738c6 100644
--- a/packages/SystemUI/src/com/android/keyguard/logging/BiometricMessageDeferralLogger.kt
+++ b/packages/SystemUI/src/com/android/keyguard/logging/BiometricMessageDeferralLogger.kt
@@ -18,7 +18,7 @@
 
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.log.LogBuffer
-import com.android.systemui.log.LogLevel.DEBUG
+import com.android.systemui.log.core.LogLevel.DEBUG
 import com.android.systemui.log.dagger.BiometricLog
 import javax.inject.Inject
 
diff --git a/packages/SystemUI/src/com/android/keyguard/logging/BiometricUnlockLogger.kt b/packages/SystemUI/src/com/android/keyguard/logging/BiometricUnlockLogger.kt
index c00b2c6..cfad614 100644
--- a/packages/SystemUI/src/com/android/keyguard/logging/BiometricUnlockLogger.kt
+++ b/packages/SystemUI/src/com/android/keyguard/logging/BiometricUnlockLogger.kt
@@ -19,9 +19,9 @@
 import android.hardware.biometrics.BiometricSourceType
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.log.LogBuffer
-import com.android.systemui.log.LogLevel
-import com.android.systemui.log.LogLevel.DEBUG
-import com.android.systemui.log.LogLevel.INFO
+import com.android.systemui.log.core.LogLevel
+import com.android.systemui.log.core.LogLevel.DEBUG
+import com.android.systemui.log.core.LogLevel.INFO
 import com.android.systemui.log.dagger.BiometricLog
 import com.android.systemui.statusbar.phone.BiometricUnlockController.MODE_DISMISS_BOUNCER
 import com.android.systemui.statusbar.phone.BiometricUnlockController.MODE_NONE
diff --git a/packages/SystemUI/src/com/android/keyguard/logging/CarrierTextManagerLogger.kt b/packages/SystemUI/src/com/android/keyguard/logging/CarrierTextManagerLogger.kt
index 1978715..d02b72f 100644
--- a/packages/SystemUI/src/com/android/keyguard/logging/CarrierTextManagerLogger.kt
+++ b/packages/SystemUI/src/com/android/keyguard/logging/CarrierTextManagerLogger.kt
@@ -19,7 +19,7 @@
 import androidx.annotation.IntDef
 import com.android.keyguard.CarrierTextManager.CarrierTextCallbackInfo
 import com.android.systemui.log.LogBuffer
-import com.android.systemui.log.LogLevel
+import com.android.systemui.log.core.LogLevel
 import com.android.systemui.log.dagger.CarrierTextManagerLog
 import javax.inject.Inject
 
diff --git a/packages/SystemUI/src/com/android/keyguard/logging/KeyguardLogger.kt b/packages/SystemUI/src/com/android/keyguard/logging/KeyguardLogger.kt
index 8b925b1..bddf3b0 100644
--- a/packages/SystemUI/src/com/android/keyguard/logging/KeyguardLogger.kt
+++ b/packages/SystemUI/src/com/android/keyguard/logging/KeyguardLogger.kt
@@ -19,7 +19,7 @@
 import com.android.systemui.biometrics.AuthRippleController
 import com.android.systemui.keyguard.KeyguardIndicationRotateTextViewController
 import com.android.systemui.log.LogBuffer
-import com.android.systemui.log.LogLevel
+import com.android.systemui.log.core.LogLevel
 import com.android.systemui.log.dagger.KeyguardLog
 import com.android.systemui.statusbar.KeyguardIndicationController
 import com.google.errorprone.annotations.CompileTimeConstant
diff --git a/packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt b/packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt
index a192803..eec5b3e 100644
--- a/packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt
+++ b/packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt
@@ -18,6 +18,7 @@
 
 import android.content.Intent
 import android.hardware.biometrics.BiometricConstants.LockoutMode
+import android.hardware.biometrics.BiometricSourceType
 import android.os.PowerManager
 import android.os.PowerManager.WakeReason
 import android.telephony.ServiceState
@@ -32,12 +33,12 @@
 import com.android.keyguard.TrustGrantFlags
 import com.android.settingslib.fuelgauge.BatteryStatus
 import com.android.systemui.log.LogBuffer
-import com.android.systemui.log.LogLevel
-import com.android.systemui.log.LogLevel.DEBUG
-import com.android.systemui.log.LogLevel.ERROR
-import com.android.systemui.log.LogLevel.INFO
-import com.android.systemui.log.LogLevel.VERBOSE
-import com.android.systemui.log.LogLevel.WARNING
+import com.android.systemui.log.core.LogLevel
+import com.android.systemui.log.core.LogLevel.DEBUG
+import com.android.systemui.log.core.LogLevel.ERROR
+import com.android.systemui.log.core.LogLevel.INFO
+import com.android.systemui.log.core.LogLevel.VERBOSE
+import com.android.systemui.log.core.LogLevel.WARNING
 import com.android.systemui.log.dagger.KeyguardUpdateMonitorLog
 import com.google.errorprone.annotations.CompileTimeConstant
 import javax.inject.Inject
@@ -370,16 +371,14 @@
 
     fun logServiceProvidersUpdated(intent: Intent) {
         logBuffer.log(
-                TAG,
-                VERBOSE,
-                {
-                    int1 = intent.getIntExtra(EXTRA_SUBSCRIPTION_INDEX, INVALID_SUBSCRIPTION_ID)
-                    str1 = intent.getStringExtra(TelephonyManager.EXTRA_SPN)
-                    str2 = intent.getStringExtra(TelephonyManager.EXTRA_PLMN)
-                },
-                {
-                    "action SERVICE_PROVIDERS_UPDATED subId=$int1 spn=$str1 plmn=$str2"
-                }
+            TAG,
+            VERBOSE,
+            {
+                int1 = intent.getIntExtra(EXTRA_SUBSCRIPTION_INDEX, INVALID_SUBSCRIPTION_ID)
+                str1 = intent.getStringExtra(TelephonyManager.EXTRA_SPN)
+                str2 = intent.getStringExtra(TelephonyManager.EXTRA_PLMN)
+            },
+            { "action SERVICE_PROVIDERS_UPDATED subId=$int1 spn=$str1 plmn=$str2" }
         )
     }
 
@@ -719,4 +718,13 @@
     fun scheduleWatchdog(@CompileTimeConstant watchdogType: String) {
         logBuffer.log(TAG, DEBUG, "Scheduling biometric watchdog for $watchdogType")
     }
+
+    fun notifyAboutEnrollmentsChanged(biometricSourceType: BiometricSourceType) {
+        logBuffer.log(
+            TAG,
+            DEBUG,
+            { str1 = "$biometricSourceType" },
+            { "notifying about enrollments changed: $str1" }
+        )
+    }
 }
diff --git a/packages/SystemUI/src/com/android/keyguard/logging/TrustRepositoryLogger.kt b/packages/SystemUI/src/com/android/keyguard/logging/TrustRepositoryLogger.kt
index cb764a8..57b5db1 100644
--- a/packages/SystemUI/src/com/android/keyguard/logging/TrustRepositoryLogger.kt
+++ b/packages/SystemUI/src/com/android/keyguard/logging/TrustRepositoryLogger.kt
@@ -21,8 +21,8 @@
 import com.android.systemui.keyguard.shared.model.TrustManagedModel
 import com.android.systemui.keyguard.shared.model.TrustModel
 import com.android.systemui.log.LogBuffer
-import com.android.systemui.log.LogLevel
-import com.android.systemui.log.LogLevel.DEBUG
+import com.android.systemui.log.core.LogLevel
+import com.android.systemui.log.core.LogLevel.DEBUG
 import com.android.systemui.log.dagger.KeyguardUpdateMonitorLog
 import javax.inject.Inject
 
diff --git a/packages/SystemUI/src/com/android/systemui/authentication/AuthenticationModule.kt b/packages/SystemUI/src/com/android/systemui/authentication/AuthenticationModule.kt
index 7c394a6..6128d91 100644
--- a/packages/SystemUI/src/com/android/systemui/authentication/AuthenticationModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/authentication/AuthenticationModule.kt
@@ -16,8 +16,11 @@
 
 package com.android.systemui.authentication
 
+import com.android.keyguard.KeyguardSecurityModel
 import com.android.systemui.authentication.data.repository.AuthenticationRepositoryModule
 import dagger.Module
+import dagger.Provides
+import java.util.function.Function
 
 @Module(
     includes =
@@ -25,4 +28,12 @@
             AuthenticationRepositoryModule::class,
         ],
 )
-object AuthenticationModule
+object AuthenticationModule {
+
+    @Provides
+    fun getSecurityMode(
+        model: KeyguardSecurityModel,
+    ): Function<Int, KeyguardSecurityModel.SecurityMode> {
+        return Function { userId -> model.getSecurityMode(userId) }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/authentication/data/repository/AuthenticationRepository.kt b/packages/SystemUI/src/com/android/systemui/authentication/data/repository/AuthenticationRepository.kt
index 0530aed..0b25184 100644
--- a/packages/SystemUI/src/com/android/systemui/authentication/data/repository/AuthenticationRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/authentication/data/repository/AuthenticationRepository.kt
@@ -16,13 +16,25 @@
 
 package com.android.systemui.authentication.data.repository
 
+import com.android.internal.widget.LockPatternUtils
+import com.android.keyguard.KeyguardSecurityModel
 import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.keyguard.data.repository.KeyguardRepository
+import com.android.systemui.user.data.repository.UserRepository
 import dagger.Binds
 import dagger.Module
+import java.util.function.Function
 import javax.inject.Inject
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.SharingStarted
 import kotlinx.coroutines.flow.StateFlow
 import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.flow.stateIn
+import kotlinx.coroutines.withContext
 
 /** Defines interface for classes that can access authentication-related application state. */
 interface AuthenticationRepository {
@@ -38,12 +50,6 @@
     val isUnlocked: StateFlow<Boolean>
 
     /**
-     * The currently-configured authentication method. This determines how the authentication
-     * challenge is completed in order to unlock an otherwise locked device.
-     */
-    val authenticationMethod: StateFlow<AuthenticationMethodModel>
-
-    /**
      * Whether lock screen bypass is enabled. When enabled, the lock screen will be automatically
      * dismisses once the authentication challenge is completed. For example, completing a biometric
      * authentication challenge via face unlock or fingerprint sensor can automatically bypass the
@@ -57,11 +63,11 @@
      */
     val failedAuthenticationAttempts: StateFlow<Int>
 
-    /** See [isUnlocked]. */
-    fun setUnlocked(isUnlocked: Boolean)
-
-    /** See [authenticationMethod]. */
-    fun setAuthenticationMethod(authenticationMethod: AuthenticationMethodModel)
+    /**
+     * Returns the currently-configured authentication method. This determines how the
+     * authentication challenge is completed in order to unlock an otherwise locked device.
+     */
+    suspend fun getAuthenticationMethod(): AuthenticationMethodModel
 
     /** See [isBypassEnabled]. */
     fun setBypassEnabled(isBypassEnabled: Boolean)
@@ -70,18 +76,23 @@
     fun setFailedAuthenticationAttempts(failedAuthenticationAttempts: Int)
 }
 
-class AuthenticationRepositoryImpl @Inject constructor() : AuthenticationRepository {
-    // TODO(b/280883900): get data from real data sources in SysUI.
+class AuthenticationRepositoryImpl
+@Inject
+constructor(
+    @Application private val applicationScope: CoroutineScope,
+    private val getSecurityMode: Function<Int, KeyguardSecurityModel.SecurityMode>,
+    @Background private val backgroundDispatcher: CoroutineDispatcher,
+    private val userRepository: UserRepository,
+    private val lockPatternUtils: LockPatternUtils,
+    keyguardRepository: KeyguardRepository,
+) : AuthenticationRepository {
 
-    private val _isUnlocked = MutableStateFlow(false)
-    override val isUnlocked: StateFlow<Boolean> = _isUnlocked.asStateFlow()
-
-    private val _authenticationMethod =
-        MutableStateFlow<AuthenticationMethodModel>(
-            AuthenticationMethodModel.Pin(listOf(1, 2, 3, 4), autoConfirm = false)
+    override val isUnlocked: StateFlow<Boolean> =
+        keyguardRepository.isKeyguardUnlocked.stateIn(
+            scope = applicationScope,
+            started = SharingStarted.WhileSubscribed(),
+            initialValue = false,
         )
-    override val authenticationMethod: StateFlow<AuthenticationMethodModel> =
-        _authenticationMethod.asStateFlow()
 
     private val _isBypassEnabled = MutableStateFlow(false)
     override val isBypassEnabled: StateFlow<Boolean> = _isBypassEnabled.asStateFlow()
@@ -90,18 +101,45 @@
     override val failedAuthenticationAttempts: StateFlow<Int> =
         _failedAuthenticationAttempts.asStateFlow()
 
-    override fun setUnlocked(isUnlocked: Boolean) {
-        _isUnlocked.value = isUnlocked
+    override suspend fun getAuthenticationMethod(): AuthenticationMethodModel {
+        return withContext(backgroundDispatcher) {
+            val selectedUserId = userRepository.getSelectedUserInfo().id
+            when (getSecurityMode.apply(selectedUserId)) {
+                KeyguardSecurityModel.SecurityMode.PIN,
+                KeyguardSecurityModel.SecurityMode.SimPin ->
+                    AuthenticationMethodModel.Pin(
+                        code = listOf(1, 2, 3, 4), // TODO(b/280883900): remove this
+                        autoConfirm = lockPatternUtils.isAutoPinConfirmEnabled(selectedUserId),
+                    )
+                KeyguardSecurityModel.SecurityMode.Password,
+                KeyguardSecurityModel.SecurityMode.SimPuk ->
+                    AuthenticationMethodModel.Password(
+                        password = "password", // TODO(b/280883900): remove this
+                    )
+                KeyguardSecurityModel.SecurityMode.Pattern ->
+                    AuthenticationMethodModel.Pattern(
+                        coordinates =
+                            listOf(
+                                AuthenticationMethodModel.Pattern.PatternCoordinate(2, 0),
+                                AuthenticationMethodModel.Pattern.PatternCoordinate(2, 1),
+                                AuthenticationMethodModel.Pattern.PatternCoordinate(2, 2),
+                                AuthenticationMethodModel.Pattern.PatternCoordinate(1, 1),
+                                AuthenticationMethodModel.Pattern.PatternCoordinate(0, 0),
+                                AuthenticationMethodModel.Pattern.PatternCoordinate(0, 1),
+                                AuthenticationMethodModel.Pattern.PatternCoordinate(0, 2),
+                            ), // TODO(b/280883900): remove this
+                    )
+                KeyguardSecurityModel.SecurityMode.None -> AuthenticationMethodModel.None
+                KeyguardSecurityModel.SecurityMode.Invalid -> error("Invalid security mode!")
+                null -> error("Invalid security is null!")
+            }
+        }
     }
 
     override fun setBypassEnabled(isBypassEnabled: Boolean) {
         _isBypassEnabled.value = isBypassEnabled
     }
 
-    override fun setAuthenticationMethod(authenticationMethod: AuthenticationMethodModel) {
-        _authenticationMethod.value = authenticationMethod
-    }
-
     override fun setFailedAuthenticationAttempts(failedAuthenticationAttempts: Int) {
         _failedAuthenticationAttempts.value = failedAuthenticationAttempts
     }
diff --git a/packages/SystemUI/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractor.kt b/packages/SystemUI/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractor.kt
index 20e82f7..15e579d 100644
--- a/packages/SystemUI/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractor.kt
@@ -25,9 +25,8 @@
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.flow.SharingStarted
 import kotlinx.coroutines.flow.StateFlow
-import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.map
 import kotlinx.coroutines.flow.stateIn
-import kotlinx.coroutines.launch
 
 /** Hosts application business logic related to authentication. */
 @SysUISingleton
@@ -38,12 +37,6 @@
     private val repository: AuthenticationRepository,
 ) {
     /**
-     * The currently-configured authentication method. This determines how the authentication
-     * challenge is completed in order to unlock an otherwise locked device.
-     */
-    val authenticationMethod: StateFlow<AuthenticationMethodModel> = repository.authenticationMethod
-
-    /**
      * Whether the device is unlocked.
      *
      * A device that is not yet unlocked requires unlocking by completing an authentication
@@ -52,20 +45,18 @@
      * Note that this state has no real bearing on whether the lock screen is showing or dismissed.
      */
     val isUnlocked: StateFlow<Boolean> =
-        combine(authenticationMethod, repository.isUnlocked) { authMethod, isUnlocked ->
-                isUnlockedWithAuthMethod(
-                    isUnlocked = isUnlocked,
-                    authMethod = authMethod,
-                )
+        repository.isUnlocked
+            .map { isUnlocked ->
+                if (getAuthenticationMethod() is AuthenticationMethodModel.None) {
+                    true
+                } else {
+                    isUnlocked
+                }
             }
             .stateIn(
                 scope = applicationScope,
                 started = SharingStarted.Eagerly,
-                initialValue =
-                    isUnlockedWithAuthMethod(
-                        isUnlocked = repository.isUnlocked.value,
-                        authMethod = repository.authenticationMethod.value,
-                    )
+                initialValue = true,
             )
 
     /**
@@ -82,41 +73,20 @@
      */
     val failedAuthenticationAttempts: StateFlow<Int> = repository.failedAuthenticationAttempts
 
-    init {
-        // UNLOCKS WHEN AUTH METHOD REMOVED.
-        //
-        // Unlocks the device if the auth method becomes None.
-        applicationScope.launch {
-            repository.authenticationMethod.collect {
-                if (it is AuthenticationMethodModel.None) {
-                    unlockDevice()
-                }
-            }
-        }
+    /**
+     * Returns the currently-configured authentication method. This determines how the
+     * authentication challenge is completed in order to unlock an otherwise locked device.
+     */
+    suspend fun getAuthenticationMethod(): AuthenticationMethodModel {
+        return repository.getAuthenticationMethod()
     }
 
     /**
      * Returns `true` if the device currently requires authentication before content can be viewed;
      * `false` if content can be displayed without unlocking first.
      */
-    fun isAuthenticationRequired(): Boolean {
-        return !isUnlocked.value && authenticationMethod.value.isSecure
-    }
-
-    /**
-     * Unlocks the device, assuming that the authentication challenge has been completed
-     * successfully.
-     */
-    fun unlockDevice() {
-        repository.setUnlocked(true)
-    }
-
-    /**
-     * Locks the device. From now on, the device will remain locked until [authenticate] is called
-     * with the correct input.
-     */
-    fun lockDevice() {
-        repository.setUnlocked(false)
+    suspend fun isAuthenticationRequired(): Boolean {
+        return !isUnlocked.value && getAuthenticationMethod().isSecure
     }
 
     /**
@@ -133,8 +103,8 @@
      * @return `true` if the authentication succeeded and the device is now unlocked; `false` when
      *   authentication failed, `null` if the check was not performed.
      */
-    fun authenticate(input: List<Any>, tryAutoConfirm: Boolean = false): Boolean? {
-        val authMethod = this.authenticationMethod.value
+    suspend fun authenticate(input: List<Any>, tryAutoConfirm: Boolean = false): Boolean? {
+        val authMethod = getAuthenticationMethod()
         if (tryAutoConfirm) {
             if ((authMethod as? AuthenticationMethodModel.Pin)?.autoConfirm != true) {
                 // Do not attempt to authenticate unless the PIN lock is set to auto-confirm.
@@ -160,7 +130,6 @@
 
         if (isSuccessful) {
             repository.setFailedAuthenticationAttempts(0)
-            repository.setUnlocked(true)
         } else {
             repository.setFailedAuthenticationAttempts(
                 repository.failedAuthenticationAttempts.value + 1
@@ -170,34 +139,12 @@
         return isSuccessful
     }
 
-    /** Triggers a biometric-powered unlock of the device. */
-    fun biometricUnlock() {
-        // TODO(b/280883900): only allow this if the biometric is enabled and there's a match.
-        repository.setUnlocked(true)
-    }
-
-    /** See [authenticationMethod]. */
-    fun setAuthenticationMethod(authenticationMethod: AuthenticationMethodModel) {
-        repository.setAuthenticationMethod(authenticationMethod)
-    }
-
     /** See [isBypassEnabled]. */
     fun toggleBypassEnabled() {
         repository.setBypassEnabled(!repository.isBypassEnabled.value)
     }
 
     companion object {
-        private fun isUnlockedWithAuthMethod(
-            isUnlocked: Boolean,
-            authMethod: AuthenticationMethodModel,
-        ): Boolean {
-            return if (authMethod is AuthenticationMethodModel.None) {
-                true
-            } else {
-                isUnlocked
-            }
-        }
-
         /**
          * Returns a PIN code from the given list. It's assumed the given list elements are all
          * [Int] in the range [0-9].
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt
index 6f0f633..946ddba 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt
@@ -37,7 +37,7 @@
 import com.android.systemui.flags.FeatureFlags
 import com.android.systemui.flags.Flags
 import com.android.systemui.keyguard.WakefulnessLifecycle
-import com.android.systemui.log.LogLevel
+import com.android.systemui.log.core.LogLevel
 import com.android.systemui.plugins.statusbar.StatusBarStateController
 import com.android.systemui.statusbar.CircleReveal
 import com.android.systemui.statusbar.LiftReveal
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
index 0dc7974..dc9ba87 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
@@ -75,6 +75,8 @@
 import com.android.systemui.biometrics.udfps.SinglePointerTouchProcessor;
 import com.android.systemui.biometrics.udfps.TouchProcessor;
 import com.android.systemui.biometrics.udfps.TouchProcessorResult;
+import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor;
+import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor;
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.doze.DozeReceiver;
@@ -82,9 +84,7 @@
 import com.android.systemui.flags.FeatureFlags;
 import com.android.systemui.flags.Flags;
 import com.android.systemui.keyguard.ScreenLifecycle;
-import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor;
 import com.android.systemui.keyguard.domain.interactor.KeyguardFaceAuthInteractor;
-import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor;
 import com.android.systemui.log.SessionTracker;
 import com.android.systemui.plugins.FalsingManager;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
@@ -189,6 +189,8 @@
     @Nullable private VelocityTracker mVelocityTracker;
     // The ID of the pointer for which ACTION_DOWN has occurred. -1 means no pointer is active.
     private int mActivePointerId = -1;
+    // Whether a pointer has been pilfered for current gesture
+    private boolean mPointerPilfered = false;
     // The timestamp of the most recent touch log.
     private long mTouchLogTime;
     // The timestamp of the most recent log of a touch InteractionEvent.
@@ -258,10 +260,6 @@
         @Override
         public void showUdfpsOverlay(long requestId, int sensorId, int reason,
                 @NonNull IUdfpsOverlayControllerCallback callback) {
-            if (mFeatureFlags.isEnabled(Flags.NEW_UDFPS_OVERLAY)) {
-                return;
-            }
-
             mFgExecutor.execute(() -> UdfpsController.this.showUdfpsOverlay(
                     new UdfpsControllerOverlay(mContext, mFingerprintManager, mInflater,
                             mWindowManager, mAccessibilityManager, mStatusBarStateController,
@@ -279,10 +277,6 @@
 
         @Override
         public void hideUdfpsOverlay(int sensorId) {
-            if (mFeatureFlags.isEnabled(Flags.NEW_UDFPS_OVERLAY)) {
-                return;
-            }
-
             mFgExecutor.execute(() -> {
                 if (mKeyguardUpdateMonitor.isFingerprintDetectionRunning()) {
                     // if we get here, we expect keyguardUpdateMonitor's fingerprintRunningState
@@ -560,6 +554,11 @@
                 || mPrimaryBouncerInteractor.isInTransit()) {
             return false;
         }
+        if (event.getAction() == MotionEvent.ACTION_DOWN
+                || event.getAction() == MotionEvent.ACTION_HOVER_ENTER) {
+            // Reset on ACTION_DOWN, start of new gesture
+            mPointerPilfered = false;
+        }
 
         final TouchProcessorResult result = mTouchProcessor.processTouch(event, mActivePointerId,
                 mOverlayParams);
@@ -633,10 +632,11 @@
             shouldPilfer = true;
         }
 
-        // Execute the pilfer
-        if (shouldPilfer) {
+        // Pilfer only once per gesture
+        if (shouldPilfer && !mPointerPilfered) {
             mInputManager.pilferPointers(
                     mOverlay.getOverlayView().getViewRootImpl().getInputToken());
+            mPointerPilfered = true;
         }
 
         return processedTouch.getTouchData().isWithinBounds(mOverlayParams.getNativeSensorBounds());
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsLogger.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsLogger.kt
index 39199d1..2102a1f 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsLogger.kt
@@ -17,10 +17,10 @@
 package com.android.systemui.biometrics
 
 import com.android.systemui.log.LogBuffer
-import com.android.systemui.log.LogLevel
-import com.android.systemui.log.LogLevel.ERROR
-import com.android.systemui.log.LogLevel.VERBOSE
-import com.android.systemui.log.LogLevel.WARNING
+import com.android.systemui.log.core.LogLevel
+import com.android.systemui.log.core.LogLevel.ERROR
+import com.android.systemui.log.core.LogLevel.VERBOSE
+import com.android.systemui.log.core.LogLevel.WARNING
 import com.android.systemui.log.dagger.UdfpsLog
 import com.google.errorprone.annotations.CompileTimeConstant
 import javax.inject.Inject
diff --git a/packages/SystemUI/src/com/android/systemui/bluetooth/BluetoothLogger.kt b/packages/SystemUI/src/com/android/systemui/bluetooth/BluetoothLogger.kt
index 96af42b..2a457eb 100644
--- a/packages/SystemUI/src/com/android/systemui/bluetooth/BluetoothLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/bluetooth/BluetoothLogger.kt
@@ -18,7 +18,7 @@
 
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.log.LogBuffer
-import com.android.systemui.log.LogLevel
+import com.android.systemui.log.core.LogLevel
 import com.android.systemui.log.dagger.BluetoothLog
 import javax.inject.Inject
 
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractor.kt b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractor.kt
index 256c635..5dd24b2 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractor.kt
@@ -35,6 +35,9 @@
 import kotlinx.coroutines.flow.SharingStarted
 import kotlinx.coroutines.flow.StateFlow
 import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.flowOf
 import kotlinx.coroutines.flow.stateIn
 import kotlinx.coroutines.launch
 
@@ -68,44 +71,44 @@
                     )
             )
 
-    /**
-     * The currently-configured authentication method. This determines how the authentication
-     * challenge is completed in order to unlock an otherwise locked device.
-     */
-    val authenticationMethod: StateFlow<AuthenticationMethodModel> =
-        authenticationInteractor.authenticationMethod
-
     /** The current authentication throttling state. If `null`, there's no throttling. */
     val throttling: StateFlow<AuthenticationThrottledModel?> = repository.throttling
 
     init {
+        // UNLOCKING SHOWS Gone.
+        //
+        // Move to the gone scene if the device becomes unlocked while on the bouncer scene.
         applicationScope.launch {
-            combine(
-                    sceneInteractor.currentScene(containerName),
-                    authenticationInteractor.authenticationMethod,
-                    ::Pair,
-                )
-                .collect { (currentScene, authMethod) ->
+            sceneInteractor
+                .currentScene(containerName)
+                .flatMapLatest { currentScene ->
                     if (currentScene.key == SceneKey.Bouncer) {
-                        when (authMethod) {
-                            is AuthenticationMethodModel.None ->
-                                sceneInteractor.setCurrentScene(
-                                    containerName,
-                                    SceneModel(SceneKey.Gone),
-                                )
-                            is AuthenticationMethodModel.Swipe ->
-                                sceneInteractor.setCurrentScene(
-                                    containerName,
-                                    SceneModel(SceneKey.Lockscreen),
-                                )
-                            else -> Unit
-                        }
+                        authenticationInteractor.isUnlocked
+                    } else {
+                        flowOf(false)
+                    }
+                }
+                .distinctUntilChanged()
+                .collect { isUnlocked ->
+                    if (isUnlocked) {
+                        sceneInteractor.setCurrentScene(
+                            containerName = containerName,
+                            scene = SceneModel(SceneKey.Gone),
+                        )
                     }
                 }
         }
     }
 
     /**
+     * Returns the currently-configured authentication method. This determines how the
+     * authentication challenge is completed in order to unlock an otherwise locked device.
+     */
+    suspend fun getAuthenticationMethod(): AuthenticationMethodModel {
+        return authenticationInteractor.getAuthenticationMethod()
+    }
+
+    /**
      * Either shows the bouncer or unlocks the device, if the bouncer doesn't need to be shown.
      *
      * @param containerName The name of the scene container to show the bouncer in.
@@ -115,18 +118,19 @@
         containerName: String,
         message: String? = null,
     ) {
-        if (authenticationInteractor.isAuthenticationRequired()) {
-            repository.setMessage(message ?: promptMessage(authenticationMethod.value))
-            sceneInteractor.setCurrentScene(
-                containerName = containerName,
-                scene = SceneModel(SceneKey.Bouncer),
-            )
-        } else {
-            authenticationInteractor.unlockDevice()
-            sceneInteractor.setCurrentScene(
-                containerName = containerName,
-                scene = SceneModel(SceneKey.Gone),
-            )
+        applicationScope.launch {
+            if (authenticationInteractor.isAuthenticationRequired()) {
+                repository.setMessage(message ?: promptMessage(getAuthenticationMethod()))
+                sceneInteractor.setCurrentScene(
+                    containerName = containerName,
+                    scene = SceneModel(SceneKey.Bouncer),
+                )
+            } else {
+                sceneInteractor.setCurrentScene(
+                    containerName = containerName,
+                    scene = SceneModel(SceneKey.Gone),
+                )
+            }
         }
     }
 
@@ -135,7 +139,7 @@
      * method.
      */
     fun resetMessage() {
-        repository.setMessage(promptMessage(authenticationMethod.value))
+        applicationScope.launch { repository.setMessage(promptMessage(getAuthenticationMethod())) }
     }
 
     /** Removes the user-facing message. */
@@ -160,7 +164,7 @@
      * @return `true` if the authentication succeeded and the device is now unlocked; `false` when
      *   authentication failed, `null` if the check was not performed.
      */
-    fun authenticate(
+    suspend fun authenticate(
         input: List<Any>,
         tryAutoConfirm: Boolean = false,
     ): Boolean? {
@@ -198,7 +202,7 @@
                     repository.setThrottling(null)
                     clearMessage()
                 }
-            else -> repository.setMessage(errorMessage(authenticationMethod.value))
+            else -> repository.setMessage(errorMessage(getAuthenticationMethod()))
         }
 
         return isAuthenticated
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModel.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModel.kt
index 527fe6e..b293ea6 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModel.kt
@@ -22,10 +22,13 @@
 import com.android.systemui.bouncer.domain.interactor.BouncerInteractor
 import com.android.systemui.bouncer.shared.model.AuthenticationThrottledModel
 import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.util.kotlin.pairwise
 import dagger.assisted.Assisted
 import dagger.assisted.AssistedFactory
 import dagger.assisted.AssistedInject
 import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.channels.BufferOverflow
+import kotlinx.coroutines.flow.MutableSharedFlow
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.SharingStarted
 import kotlinx.coroutines.flow.StateFlow
@@ -66,6 +69,7 @@
 
     private val password: PasswordBouncerViewModel by lazy {
         PasswordBouncerViewModel(
+            applicationScope = applicationScope,
             interactor = interactor,
             isInputEnabled = isInputEnabled,
         )
@@ -81,14 +85,30 @@
     }
 
     /** View-model for the current UI, based on the current authentication method. */
+    private val _authMethod =
+        MutableSharedFlow<AuthMethodBouncerViewModel?>(
+            replay = 1,
+            onBufferOverflow = BufferOverflow.DROP_OLDEST,
+        )
     val authMethod: StateFlow<AuthMethodBouncerViewModel?> =
-        interactor.authenticationMethod
-            .map { authMethod -> toViewModel(authMethod) }
-            .stateIn(
-                scope = applicationScope,
-                started = SharingStarted.WhileSubscribed(),
-                initialValue = toViewModel(interactor.authenticationMethod.value),
-            )
+        _authMethod.stateIn(
+            scope = applicationScope,
+            started = SharingStarted.WhileSubscribed(),
+            initialValue = null,
+        )
+
+    init {
+        applicationScope.launch {
+            _authMethod.subscriptionCount
+                .pairwise()
+                .map { (previousCount, currentCount) -> currentCount > previousCount }
+                .collect { subscriberAdded ->
+                    if (subscriberAdded) {
+                        reloadAuthMethod()
+                    }
+                }
+        }
+    }
 
     /** The user-facing message to show in the bouncer. */
     val message: StateFlow<MessageViewModel> =
@@ -125,7 +145,7 @@
             interactor.throttling
                 .map { model ->
                     model?.let {
-                        when (interactor.authenticationMethod.value) {
+                        when (interactor.getAuthenticationMethod()) {
                             is AuthenticationMethodModel.Pin ->
                                 R.string.kg_too_many_failed_pin_attempts_dialog_message
                             is AuthenticationMethodModel.Password ->
@@ -161,17 +181,6 @@
         _throttlingDialogMessage.value = null
     }
 
-    private fun toViewModel(
-        authMethod: AuthenticationMethodModel,
-    ): AuthMethodBouncerViewModel? {
-        return when (authMethod) {
-            is AuthenticationMethodModel.Pin -> pin
-            is AuthenticationMethodModel.Password -> password
-            is AuthenticationMethodModel.Pattern -> pattern
-            else -> null
-        }
-    }
-
     private fun toMessageViewModel(
         message: String?,
         throttling: AuthenticationThrottledModel?,
@@ -182,6 +191,17 @@
         )
     }
 
+    private suspend fun reloadAuthMethod() {
+        _authMethod.tryEmit(
+            when (interactor.getAuthenticationMethod()) {
+                is AuthenticationMethodModel.Pin -> pin
+                is AuthenticationMethodModel.Password -> password
+                is AuthenticationMethodModel.Pattern -> pattern
+                else -> null
+            }
+        )
+    }
+
     data class MessageViewModel(
         val text: String,
 
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModel.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModel.kt
index 0146e40..ca15f4e 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModel.kt
@@ -17,12 +17,15 @@
 package com.android.systemui.bouncer.ui.viewmodel
 
 import com.android.systemui.bouncer.domain.interactor.BouncerInteractor
+import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.StateFlow
 import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.launch
 
 /** Holds UI state and handles user input for the password bouncer UI. */
 class PasswordBouncerViewModel(
+    private val applicationScope: CoroutineScope,
     private val interactor: BouncerInteractor,
     isInputEnabled: StateFlow<Boolean>,
 ) :
@@ -50,10 +53,13 @@
 
     /** Notifies that the user has pressed the key for attempting to authenticate the password. */
     fun onAuthenticateKeyPressed() {
-        if (interactor.authenticate(password.value.toCharArray().toList()) != true) {
-            showFailureAnimation()
-        }
-
+        val password = _password.value.toCharArray().toList()
         _password.value = ""
+
+        applicationScope.launch {
+            if (interactor.authenticate(password) != true) {
+                showFailureAnimation()
+            }
+        }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModel.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModel.kt
index 700703e..5efa6f0 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModel.kt
@@ -29,13 +29,15 @@
 import kotlinx.coroutines.flow.SharingStarted
 import kotlinx.coroutines.flow.StateFlow
 import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.flow.flow
 import kotlinx.coroutines.flow.map
 import kotlinx.coroutines.flow.stateIn
+import kotlinx.coroutines.launch
 
 /** Holds UI state and handles user input for the pattern bouncer UI. */
 class PatternBouncerViewModel(
     private val applicationContext: Context,
-    applicationScope: CoroutineScope,
+    private val applicationScope: CoroutineScope,
     private val interactor: BouncerInteractor,
     isInputEnabled: StateFlow<Boolean>,
 ) :
@@ -69,12 +71,17 @@
 
     /** Whether the pattern itself should be rendered visibly. */
     val isPatternVisible: StateFlow<Boolean> =
-        interactor.authenticationMethod
-            .map { authMethod -> isPatternVisible(authMethod) }
+        flow {
+                emit(null)
+                emit(interactor.getAuthenticationMethod())
+            }
+            .map { authMethod ->
+                (authMethod as? AuthenticationMethodModel.Pattern)?.isPatternVisible ?: false
+            }
             .stateIn(
                 scope = applicationScope,
                 started = SharingStarted.Eagerly,
-                initialValue = isPatternVisible(interactor.authenticationMethod.value),
+                initialValue = false,
             )
 
     /** Notifies that the UI has been shown to the user. */
@@ -154,17 +161,15 @@
     /** Notifies that the user has ended the drag gesture across the dot grid. */
     fun onDragEnd() {
         val pattern = _selectedDots.value.map { it.toCoordinate() }
-        if (interactor.authenticate(pattern) != true) {
-            showFailureAnimation()
-        }
-
         _dots.value = defaultDots()
         _currentDot.value = null
         _selectedDots.value = linkedSetOf()
-    }
 
-    private fun isPatternVisible(authMethodModel: AuthenticationMethodModel): Boolean {
-        return (authMethodModel as? AuthenticationMethodModel.Pattern)?.isPatternVisible ?: false
+        applicationScope.launch {
+            if (interactor.authenticate(pattern) != true) {
+                showFailureAnimation()
+            }
+        }
     }
 
     private fun defaultDots(): List<PatternDotViewModel> {
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModel.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModel.kt
index 1944c74..014ebc3 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModel.kt
@@ -22,13 +22,14 @@
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.SharingStarted
 import kotlinx.coroutines.flow.StateFlow
-import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.flow
 import kotlinx.coroutines.flow.map
 import kotlinx.coroutines.flow.stateIn
+import kotlinx.coroutines.launch
 
 /** Holds UI state and handles user input for the PIN code bouncer UI. */
 class PinBouncerViewModel(
-    applicationScope: CoroutineScope,
+    private val applicationScope: CoroutineScope,
     private val interactor: BouncerInteractor,
     isInputEnabled: StateFlow<Boolean>,
 ) :
@@ -39,40 +40,45 @@
     private val mutablePinEntries = MutableStateFlow<List<EnteredKey>>(emptyList())
     val pinEntries: StateFlow<List<EnteredKey>> = mutablePinEntries
 
-    /** The length of the hinted PIN, or null if pin length hint should not be shown. */
+    /** The length of the hinted PIN, or `null` if pin length hint should not be shown. */
     val hintedPinLength: StateFlow<Int?> =
-        interactor.authenticationMethod
-            .map { authMethod -> computeHintedPinLength(authMethod) }
-            .stateIn(
-                scope = applicationScope,
-                started = SharingStarted.Eagerly,
-                initialValue = computeHintedPinLength(interactor.authenticationMethod.value),
-            )
-
-    /** Appearance of the backspace button. */
-    val backspaceButtonAppearance: StateFlow<ActionButtonAppearance> =
-        combine(interactor.authenticationMethod, mutablePinEntries) { authMethod, enteredPin ->
-                computeBackspaceButtonAppearance(authMethod, enteredPin)
+        flow { emit(interactor.getAuthenticationMethod()) }
+            .map { authMethod ->
+                // Hinting is enabled for 6-digit codes only
+                autoConfirmPinLength(authMethod).takeIf { it == HINTING_PASSCODE_LENGTH }
             }
             .stateIn(
                 scope = applicationScope,
                 started = SharingStarted.Eagerly,
-                initialValue =
-                    computeBackspaceButtonAppearance(
-                        interactor.authenticationMethod.value,
-                        mutablePinEntries.value
-                    ),
+                initialValue = null,
+            )
+
+    /** Appearance of the backspace button. */
+    val backspaceButtonAppearance: StateFlow<ActionButtonAppearance> =
+        mutablePinEntries
+            .map { mutablePinEntries ->
+                computeBackspaceButtonAppearance(
+                    interactor.getAuthenticationMethod(),
+                    mutablePinEntries
+                )
+            }
+            .stateIn(
+                scope = applicationScope,
+                started = SharingStarted.Eagerly,
+                initialValue = ActionButtonAppearance.Hidden,
             )
 
     /** Appearance of the confirm button. */
     val confirmButtonAppearance: StateFlow<ActionButtonAppearance> =
-        interactor.authenticationMethod
+        flow {
+                emit(null)
+                emit(interactor.getAuthenticationMethod())
+            }
             .map { authMethod -> computeConfirmButtonAppearance(authMethod) }
             .stateIn(
                 scope = applicationScope,
                 started = SharingStarted.Eagerly,
-                initialValue =
-                    computeConfirmButtonAppearance(interactor.authenticationMethod.value),
+                initialValue = ActionButtonAppearance.Hidden,
             )
 
     /** Notifies that the UI has been shown to the user. */
@@ -111,30 +117,28 @@
 
     private fun tryAuthenticate(useAutoConfirm: Boolean) {
         val pinCode = mutablePinEntries.value.map { it.input }
-        val isSuccess = interactor.authenticate(pinCode, useAutoConfirm) ?: return
 
-        if (!isSuccess) {
-            showFailureAnimation()
+        applicationScope.launch {
+            val isSuccess = interactor.authenticate(pinCode, useAutoConfirm) ?: return@launch
+
+            if (!isSuccess) {
+                showFailureAnimation()
+            }
+
+            mutablePinEntries.value = emptyList()
         }
-
-        mutablePinEntries.value = emptyList()
     }
 
-    private fun isAutoConfirmEnabled(authMethodModel: AuthenticationMethodModel): Boolean {
+    private fun isAutoConfirmEnabled(authMethodModel: AuthenticationMethodModel?): Boolean {
         return (authMethodModel as? AuthenticationMethodModel.Pin)?.autoConfirm == true
     }
 
-    private fun autoConfirmPinLength(authMethodModel: AuthenticationMethodModel): Int? {
+    private fun autoConfirmPinLength(authMethodModel: AuthenticationMethodModel?): Int? {
         if (!isAutoConfirmEnabled(authMethodModel)) return null
 
         return (authMethodModel as? AuthenticationMethodModel.Pin)?.code?.size
     }
 
-    private fun computeHintedPinLength(authMethodModel: AuthenticationMethodModel): Int? {
-        // Hinting is enabled for 6-digit codes only
-        return autoConfirmPinLength(authMethodModel).takeIf { it == HINTING_PASSCODE_LENGTH }
-    }
-
     private fun computeBackspaceButtonAppearance(
         authMethodModel: AuthenticationMethodModel,
         enteredPin: List<EnteredKey>
@@ -149,7 +153,7 @@
         }
     }
     private fun computeConfirmButtonAppearance(
-        authMethodModel: AuthenticationMethodModel
+        authMethodModel: AuthenticationMethodModel?
     ): ActionButtonAppearance {
         return if (isAutoConfirmEnabled(authMethodModel)) {
             ActionButtonAppearance.Hidden
diff --git a/packages/SystemUI/src/com/android/systemui/broadcast/logging/BroadcastDispatcherLogger.kt b/packages/SystemUI/src/com/android/systemui/broadcast/logging/BroadcastDispatcherLogger.kt
index 5b3a982..068f329 100644
--- a/packages/SystemUI/src/com/android/systemui/broadcast/logging/BroadcastDispatcherLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/broadcast/logging/BroadcastDispatcherLogger.kt
@@ -21,10 +21,10 @@
 import android.content.Intent
 import android.content.IntentFilter
 import com.android.systemui.log.LogBuffer
-import com.android.systemui.log.LogLevel
-import com.android.systemui.log.LogLevel.DEBUG
-import com.android.systemui.log.LogLevel.INFO
-import com.android.systemui.log.LogMessage
+import com.android.systemui.log.core.LogLevel
+import com.android.systemui.log.core.LogLevel.DEBUG
+import com.android.systemui.log.core.LogLevel.INFO
+import com.android.systemui.log.core.LogMessage
 import com.android.systemui.log.dagger.BroadcastDispatcherLog
 import javax.inject.Inject
 
diff --git a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsEditingActivity.kt b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsEditingActivity.kt
index d629e3e..8e41974 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsEditingActivity.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsEditingActivity.kt
@@ -154,7 +154,7 @@
     private fun bindViews() {
         setContentView(R.layout.controls_management)
 
-        getLifecycle().addObserver(
+        lifecycle.addObserver(
             ControlsAnimations.observerForAnimations(
                 requireViewById<ViewGroup>(R.id.controls_management_root),
                 window,
diff --git a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsFavoritingActivity.kt b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsFavoritingActivity.kt
index d3ffc95..d3aa449 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsFavoritingActivity.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsFavoritingActivity.kt
@@ -268,7 +268,7 @@
     private fun bindViews() {
         setContentView(R.layout.controls_management)
 
-        getLifecycle().addObserver(
+        lifecycle.addObserver(
             ControlsAnimations.observerForAnimations(
                 requireViewById<ViewGroup>(R.id.controls_management_root),
                 window,
diff --git a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsProviderSelectorActivity.kt b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsProviderSelectorActivity.kt
index fb19ac9..d5b8693 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsProviderSelectorActivity.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsProviderSelectorActivity.kt
@@ -91,7 +91,7 @@
 
         setContentView(R.layout.controls_management)
 
-        getLifecycle().addObserver(
+        lifecycle.addObserver(
             ControlsAnimations.observerForAnimations(
                 requireViewById<ViewGroup>(R.id.controls_management_root),
                 window,
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsActivity.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsActivity.kt
index 4a22e4e..557dcf4 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsActivity.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsActivity.kt
@@ -72,7 +72,7 @@
 
         setContentView(R.layout.controls_fullscreen)
 
-        getLifecycle().addObserver(
+        lifecycle.addObserver(
             ControlsAnimations.observerForAnimations(
                 requireViewById(R.id.control_detail_root),
                 window,
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
index 9ab9e7a..8f3c3d6 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
@@ -47,6 +47,7 @@
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.dagger.qualifiers.SystemUser;
 import com.android.systemui.demomode.dagger.DemoModeModule;
+import com.android.systemui.display.DisplayModule;
 import com.android.systemui.doze.dagger.DozeComponent;
 import com.android.systemui.dreams.dagger.DreamModule;
 import com.android.systemui.dump.DumpManager;
@@ -163,6 +164,7 @@
             ClipboardOverlayModule.class,
             ClockRegistryModule.class,
             CommonRepositoryModule.class,
+            DisplayModule.class,
             ConnectivityModule.class,
             CoroutinesModule.class,
             DreamModule.class,
diff --git a/packages/SystemUI/src/com/android/systemui/display/DisplayModule.kt b/packages/SystemUI/src/com/android/systemui/display/DisplayModule.kt
new file mode 100644
index 0000000..65cd84b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/display/DisplayModule.kt
@@ -0,0 +1,35 @@
+/*
+ * 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.display
+
+import com.android.systemui.display.data.repository.DisplayRepository
+import com.android.systemui.display.data.repository.DisplayRepositoryImpl
+import com.android.systemui.display.domain.interactor.ConnectedDisplayInteractor
+import com.android.systemui.display.domain.interactor.ConnectedDisplayInteractorImpl
+import dagger.Binds
+import dagger.Module
+
+/** Module binding display related classes. */
+@Module
+interface DisplayModule {
+    @Binds
+    fun bindConnectedDisplayInteractor(
+        provider: ConnectedDisplayInteractorImpl
+    ): ConnectedDisplayInteractor
+
+    @Binds fun bindsDisplayRepository(displayRepository: DisplayRepositoryImpl): DisplayRepository
+}
diff --git a/packages/SystemUI/src/com/android/systemui/display/data/repository/DisplayMetricsRepository.kt b/packages/SystemUI/src/com/android/systemui/display/data/repository/DisplayMetricsRepository.kt
new file mode 100644
index 0000000..bcfeeb9e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/display/data/repository/DisplayMetricsRepository.kt
@@ -0,0 +1,74 @@
+/*
+ * 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.display.data.repository
+
+import android.content.Context
+import android.content.res.Configuration
+import android.util.DisplayMetrics
+import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.core.LogLevel
+import com.android.systemui.log.dagger.DisplayMetricsRepoLog
+import com.android.systemui.statusbar.policy.ConfigurationController
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.flow.stateIn
+
+/** Repository tracking display-related metrics like display height and width. */
+@SysUISingleton
+class DisplayMetricsRepository
+@Inject
+constructor(
+    @Application scope: CoroutineScope,
+    configurationController: ConfigurationController,
+    displayMetricsHolder: DisplayMetrics,
+    context: Context,
+    @DisplayMetricsRepoLog logBuffer: LogBuffer,
+) {
+
+    private val displayMetrics: StateFlow<DisplayMetrics> =
+        conflatedCallbackFlow {
+                val callback =
+                    object : ConfigurationController.ConfigurationListener {
+                        override fun onConfigChanged(newConfig: Configuration?) {
+                            context.display.getMetrics(displayMetricsHolder)
+                            trySend(displayMetricsHolder)
+                        }
+                    }
+                configurationController.addCallback(callback)
+                awaitClose { configurationController.removeCallback(callback) }
+            }
+            .onEach {
+                logBuffer.log(
+                    "DisplayMetrics",
+                    LogLevel.INFO,
+                    { str1 = it.toString() },
+                    { "New metrics: $str1" },
+                )
+            }
+            .stateIn(scope, SharingStarted.Eagerly, displayMetricsHolder)
+
+    /** Returns the current display height in pixels. */
+    val heightPixels: Int
+        get() = displayMetrics.value.heightPixels
+}
diff --git a/packages/SystemUI/src/com/android/systemui/display/domain/interactor/ConnectedDisplayInteractor.kt b/packages/SystemUI/src/com/android/systemui/display/domain/interactor/ConnectedDisplayInteractor.kt
new file mode 100644
index 0000000..4b957c7
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/display/domain/interactor/ConnectedDisplayInteractor.kt
@@ -0,0 +1,73 @@
+/*
+ * 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.display.domain.interactor
+
+import android.view.Display
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.display.data.repository.DisplayRepository
+import com.android.systemui.display.domain.interactor.ConnectedDisplayInteractor.State
+import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.map
+
+/** Provides information about an external connected display. */
+interface ConnectedDisplayInteractor {
+    /**
+     * Provides the current external display state.
+     *
+     * The state is:
+     * - [State.CONNECTED] when there is at least one display with [TYPE_EXTERNAL].
+     * - [State.CONNECTED_SECURE] when is at least one display with both [TYPE_EXTERNAL] AND
+     *   [Display.FLAG_SECURE] set
+     */
+    val connectedDisplayState: Flow<State>
+
+    /** Possible connected display state. */
+    enum class State {
+        DISCONNECTED,
+        CONNECTED,
+        CONNECTED_SECURE,
+    }
+}
+
+@SysUISingleton
+class ConnectedDisplayInteractorImpl
+@Inject
+constructor(
+    displayRepository: DisplayRepository,
+) : ConnectedDisplayInteractor {
+
+    override val connectedDisplayState: Flow<State> =
+        displayRepository.displays
+            .map { displays ->
+                val externalDisplays =
+                    displays.filter { display -> display.type == Display.TYPE_EXTERNAL }
+
+                val secureExternalDisplays =
+                    externalDisplays.filter { it.flags and Display.FLAG_SECURE != 0 }
+
+                if (externalDisplays.isEmpty()) {
+                    State.DISCONNECTED
+                } else if (!secureExternalDisplays.isEmpty()) {
+                    State.CONNECTED_SECURE
+                } else {
+                    State.CONNECTED
+                }
+            }
+            .distinctUntilChanged()
+}
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeLogger.kt b/packages/SystemUI/src/com/android/systemui/doze/DozeLogger.kt
index 5369780..75b8e51 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeLogger.kt
@@ -20,9 +20,9 @@
 import com.android.systemui.doze.DozeLog.Reason
 import com.android.systemui.doze.DozeLog.reasonToString
 import com.android.systemui.log.LogBuffer
-import com.android.systemui.log.LogLevel.DEBUG
-import com.android.systemui.log.LogLevel.ERROR
-import com.android.systemui.log.LogLevel.INFO
+import com.android.systemui.log.core.LogLevel.DEBUG
+import com.android.systemui.log.core.LogLevel.ERROR
+import com.android.systemui.log.core.LogLevel.INFO
 import com.android.systemui.log.dagger.DozeLog
 import com.android.systemui.statusbar.policy.DevicePostureController
 import com.google.errorprone.annotations.CompileTimeConstant
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamLogger.kt b/packages/SystemUI/src/com/android/systemui/dreams/DreamLogger.kt
index fdb7651..0e22406 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/DreamLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamLogger.kt
@@ -17,7 +17,7 @@
 package com.android.systemui.dreams
 
 import com.android.systemui.log.LogBuffer
-import com.android.systemui.log.LogLevel
+import com.android.systemui.log.core.LogLevel
 import com.android.systemui.log.dagger.DreamLog
 import javax.inject.Inject
 
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayLifecycleOwner.kt b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayLifecycleOwner.kt
index 8325356..003d2c7 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayLifecycleOwner.kt
+++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayLifecycleOwner.kt
@@ -28,7 +28,8 @@
 class DreamOverlayLifecycleOwner @Inject constructor() : LifecycleOwner {
     val registry: LifecycleRegistry = LifecycleRegistry(this)
 
-    override fun getLifecycle(): Lifecycle {
-        return registry
-    }
+    override val lifecycle: Lifecycle
+        get() {
+            return registry
+        }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
index 6242492..db5f546 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
@@ -151,12 +151,6 @@
     // TODO(b/255607168): Tracking Bug
     @JvmField val DOZING_MIGRATION_1 = unreleasedFlag(213, "dozing_migration_1")
 
-    // TODO(b/252897742): Tracking Bug
-    @JvmField val NEW_ELLIPSE_DETECTION = unreleasedFlag(214, "new_ellipse_detection")
-
-    // TODO(b/252897742): Tracking Bug
-    @JvmField val NEW_UDFPS_OVERLAY = unreleasedFlag(215, "new_udfps_overlay")
-
     /**
      * Whether to enable the code powering customizable lock screen quick affordances.
      *
@@ -730,6 +724,11 @@
     val SPLIT_SHADE_SUBPIXEL_OPTIMIZATION =
             releasedFlag(2805, "split_shade_subpixel_optimization")
 
+    // TODO(b/288868056): Tracking Bug
+    @JvmField
+    val PARTIAL_SCREEN_SHARING_TASK_SWITCHER =
+            unreleasedFlag(288868056, "pss_task_switcher")
+
     // TODO(b/278761837): Tracking Bug
     @JvmField
     val USE_NEW_ACTIVITY_STARTER = releasedFlag(2801, name = "use_new_activity_starter")
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt
index e8881a4..df83aaf 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt
@@ -59,7 +59,7 @@
         // At startup, 2 views with the ID `R.id.keyguard_indication_area` will be available.
         // Disable one of them
         if (featureFlags.isEnabled(Flags.MIGRATE_INDICATION_AREA)) {
-            legacyParent.requireViewById<View>(R.id.keyguard_indication_area).let {
+            legacyParent.findViewById<View>(R.id.keyguard_indication_area)?.let {
                 legacyParent.removeView(it)
             }
         } else {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index c629ebf..155e023 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -835,6 +835,11 @@
         }
 
         @Override
+        public void onBouncerSwipeDown() {
+            mKeyguardViewControllerLazy.get().reset(/* hideBouncerWhenShowing= */ true);
+        }
+
+        @Override
         public void playTrustedSound() {
             KeyguardViewMediator.this.playTrustedSound();
         }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepository.kt
index 128057a..3d8f6fd 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepository.kt
@@ -254,13 +254,17 @@
 
     private fun observeFaceDetectGatingChecks() {
         // Face detection can run only when lockscreen bypass is enabled
-        // & detection is supported & biometric unlock is not allowed.
+        // & detection is supported
+        //   & biometric unlock is not allowed
+        //     or user is trusted by trust manager & we want to run face detect to dismiss keyguard
         listOf(
                 canFaceAuthOrDetectRun(faceDetectLog),
                 logAndObserve(isBypassEnabled, "isBypassEnabled", faceDetectLog),
                 logAndObserve(
-                    biometricSettingsRepository.isNonStrongBiometricAllowed.isFalse(),
-                    "nonStrongBiometricIsNotAllowed",
+                    biometricSettingsRepository.isNonStrongBiometricAllowed
+                        .isFalse()
+                        .or(trustRepository.isCurrentUserTrusted),
+                    "nonStrongBiometricIsNotAllowedOrCurrentUserIsTrusted",
                     faceDetectLog
                 ),
                 // We don't want to run face detect if fingerprint can be used to unlock the device
@@ -312,18 +316,19 @@
                     tableLogBuffer
                 ),
                 logAndObserve(
-                    keyguardRepository.wakefulness.map { it.isStartingToSleepOrAsleep() }.isFalse(),
-                    "deviceNotSleepingOrNotStartingToSleep",
+                    keyguardRepository.wakefulness.map { it.isStartingToSleep() }.isFalse(),
+                    "deviceNotStartingToSleep",
                     tableLogBuffer
                 ),
                 logAndObserve(
-                    combine(
-                        keyguardInteractor.isSecureCameraActive,
-                        alternateBouncerInteractor.isVisible
-                    ) { a, b ->
-                        !a || b
-                    },
-                    "secureCameraNotActiveOrAltBouncerIsShowing",
+                    keyguardInteractor.isSecureCameraActive
+                        .isFalse()
+                        .or(
+                            alternateBouncerInteractor.isVisible.or(
+                                keyguardInteractor.primaryBouncerShowing
+                            )
+                        ),
+                    "secureCameraNotActiveOrAnyBouncerIsShowing",
                     tableLogBuffer
                 ),
                 logAndObserve(
@@ -365,6 +370,7 @@
                     "nonStrongBiometricIsAllowed",
                     faceAuthLog
                 ),
+                logAndObserve(isAuthenticated.isFalse(), "faceNotAuthenticated", faceAuthLog),
             )
             .reduce(::and)
             .distinctUntilChanged()
@@ -639,6 +645,10 @@
 private fun and(flow: Flow<Boolean>, anotherFlow: Flow<Boolean>) =
     flow.combine(anotherFlow) { a, b -> a && b }
 
+/** Combine two boolean flows by or-ing both of them */
+private fun Flow<Boolean>.or(anotherFlow: Flow<Boolean>) =
+    this.combine(anotherFlow) { a, b -> a || b }
+
 /** "Not" the given flow. The return [Flow] will be true when [this] flow is false. */
 private fun Flow<Boolean>.isFalse(): Flow<Boolean> {
     return this.map { !it }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt
index 81f62b6..edc0b45 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt
@@ -302,7 +302,15 @@
                             trySendWithFailureLogging(
                                 keyguardStateController.isUnlocked,
                                 TAG,
-                                "updated isKeyguardUnlocked"
+                                "updated isKeyguardUnlocked due to onUnlockedChanged"
+                            )
+                        }
+
+                        override fun onKeyguardShowingChanged() {
+                            trySendWithFailureLogging(
+                                keyguardStateController.isUnlocked,
+                                TAG,
+                                "updated isKeyguardUnlocked due to onKeyguardShowingChanged"
                             )
                         }
                     }
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 38eacce..8f0b91b 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
@@ -22,7 +22,6 @@
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
 import com.android.systemui.keyguard.shared.model.KeyguardState
-import com.android.systemui.keyguard.shared.model.TransitionInfo
 import com.android.systemui.keyguard.shared.model.WakefulnessState
 import com.android.systemui.util.kotlin.Utils.Companion.toQuad
 import com.android.systemui.util.kotlin.Utils.Companion.toQuint
@@ -38,11 +37,14 @@
 class FromAlternateBouncerTransitionInteractor
 @Inject
 constructor(
+    override val transitionRepository: KeyguardTransitionRepository,
+    override val transitionInteractor: KeyguardTransitionInteractor,
     @Application private val scope: CoroutineScope,
     private val keyguardInteractor: KeyguardInteractor,
-    private val keyguardTransitionRepository: KeyguardTransitionRepository,
-    private val keyguardTransitionInteractor: KeyguardTransitionInteractor,
-) : TransitionInteractor(FromAlternateBouncerTransitionInteractor::class.simpleName!!) {
+) :
+    TransitionInteractor(
+        fromState = KeyguardState.ALTERNATE_BOUNCER,
+    ) {
 
     override fun start() {
         listenForAlternateBouncerToGone()
@@ -60,7 +62,7 @@
                 .sample(
                     combine(
                         keyguardInteractor.primaryBouncerShowing,
-                        keyguardTransitionInteractor.startedKeyguardTransitionStep,
+                        transitionInteractor.startedKeyguardTransitionStep,
                         keyguardInteractor.wakefulnessModel,
                         keyguardInteractor.isAodAvailable,
                         ::toQuad
@@ -92,14 +94,7 @@
                             } else {
                                 KeyguardState.LOCKSCREEN
                             }
-                        keyguardTransitionRepository.startTransition(
-                            TransitionInfo(
-                                ownerName = name,
-                                from = KeyguardState.ALTERNATE_BOUNCER,
-                                to = to,
-                                animator = getAnimator(),
-                            )
-                        )
+                        startTransitionTo(to)
                     }
                 }
         }
@@ -108,17 +103,10 @@
     private fun listenForAlternateBouncerToGone() {
         scope.launch {
             keyguardInteractor.isKeyguardGoingAway
-                .sample(keyguardTransitionInteractor.finishedKeyguardState, ::Pair)
+                .sample(transitionInteractor.finishedKeyguardState, ::Pair)
                 .collect { (isKeyguardGoingAway, keyguardState) ->
                     if (isKeyguardGoingAway && keyguardState == KeyguardState.ALTERNATE_BOUNCER) {
-                        keyguardTransitionRepository.startTransition(
-                            TransitionInfo(
-                                ownerName = name,
-                                from = KeyguardState.ALTERNATE_BOUNCER,
-                                to = KeyguardState.GONE,
-                                animator = getAnimator(),
-                            )
-                        )
+                        startTransitionTo(KeyguardState.GONE)
                     }
                 }
         }
@@ -127,26 +115,19 @@
     private fun listenForAlternateBouncerToPrimaryBouncer() {
         scope.launch {
             keyguardInteractor.primaryBouncerShowing
-                .sample(keyguardTransitionInteractor.startedKeyguardTransitionStep, ::Pair)
+                .sample(transitionInteractor.startedKeyguardTransitionStep, ::Pair)
                 .collect { (isPrimaryBouncerShowing, startedKeyguardState) ->
                     if (
                         isPrimaryBouncerShowing &&
                             startedKeyguardState.to == KeyguardState.ALTERNATE_BOUNCER
                     ) {
-                        keyguardTransitionRepository.startTransition(
-                            TransitionInfo(
-                                ownerName = name,
-                                from = KeyguardState.ALTERNATE_BOUNCER,
-                                to = KeyguardState.PRIMARY_BOUNCER,
-                                animator = getAnimator(),
-                            )
-                        )
+                        startTransitionTo(KeyguardState.PRIMARY_BOUNCER)
                     }
                 }
         }
     }
 
-    private fun getAnimator(): ValueAnimator {
+    override fun getDefaultAnimatorForTransitionsToState(toState: KeyguardState): ValueAnimator {
         return ValueAnimator().apply {
             interpolator = Interpolators.LINEAR
             duration = TRANSITION_DURATION_MS
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 7e9cbc1..2085c87 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
@@ -24,7 +24,6 @@
 import com.android.systemui.keyguard.shared.model.BiometricUnlockModel.Companion.isWakeAndUnlock
 import com.android.systemui.keyguard.shared.model.DozeStateModel
 import com.android.systemui.keyguard.shared.model.KeyguardState
-import com.android.systemui.keyguard.shared.model.TransitionInfo
 import com.android.systemui.util.kotlin.sample
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineScope
@@ -34,11 +33,14 @@
 class FromAodTransitionInteractor
 @Inject
 constructor(
+    override val transitionRepository: KeyguardTransitionRepository,
+    override val transitionInteractor: KeyguardTransitionInteractor,
     @Application private val scope: CoroutineScope,
     private val keyguardInteractor: KeyguardInteractor,
-    private val keyguardTransitionRepository: KeyguardTransitionRepository,
-    private val keyguardTransitionInteractor: KeyguardTransitionInteractor,
-) : TransitionInteractor(FromAodTransitionInteractor::class.simpleName!!) {
+) :
+    TransitionInteractor(
+        fromState = KeyguardState.AOD,
+    ) {
 
     override fun start() {
         listenForAodToLockscreen()
@@ -49,18 +51,11 @@
         scope.launch {
             keyguardInteractor
                 .dozeTransitionTo(DozeStateModel.FINISH)
-                .sample(keyguardTransitionInteractor.startedKeyguardTransitionStep, ::Pair)
+                .sample(transitionInteractor.startedKeyguardTransitionStep, ::Pair)
                 .collect { pair ->
                     val (dozeToAod, lastStartedStep) = pair
                     if (lastStartedStep.to == KeyguardState.AOD) {
-                        keyguardTransitionRepository.startTransition(
-                            TransitionInfo(
-                                name,
-                                KeyguardState.AOD,
-                                KeyguardState.LOCKSCREEN,
-                                getAnimator(),
-                            )
-                        )
+                        startTransitionTo(KeyguardState.LOCKSCREEN)
                     }
                 }
         }
@@ -69,29 +64,22 @@
     private fun listenForAodToGone() {
         scope.launch {
             keyguardInteractor.biometricUnlockState
-                .sample(keyguardTransitionInteractor.finishedKeyguardState, ::Pair)
+                .sample(transitionInteractor.finishedKeyguardState, ::Pair)
                 .collect { pair ->
                     val (biometricUnlockState, keyguardState) = pair
                     if (
                         keyguardState == KeyguardState.AOD && isWakeAndUnlock(biometricUnlockState)
                     ) {
-                        keyguardTransitionRepository.startTransition(
-                            TransitionInfo(
-                                name,
-                                KeyguardState.AOD,
-                                KeyguardState.GONE,
-                                getAnimator(),
-                            )
-                        )
+                        startTransitionTo(KeyguardState.GONE)
                     }
                 }
         }
     }
 
-    private fun getAnimator(): ValueAnimator {
+    override fun getDefaultAnimatorForTransitionsToState(toState: KeyguardState): ValueAnimator {
         return ValueAnimator().apply {
-            setInterpolator(Interpolators.LINEAR)
-            setDuration(TRANSITION_DURATION_MS)
+            interpolator = Interpolators.LINEAR
+            duration = TRANSITION_DURATION_MS
         }
     }
 
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 ee2c2df..c867c43 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
@@ -23,10 +23,8 @@
 import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
 import com.android.systemui.keyguard.shared.model.BiometricUnlockModel.Companion.isWakeAndUnlock
 import com.android.systemui.keyguard.shared.model.KeyguardState
-import com.android.systemui.keyguard.shared.model.TransitionInfo
 import com.android.systemui.util.kotlin.sample
 import javax.inject.Inject
-import kotlin.time.Duration
 import kotlin.time.Duration.Companion.milliseconds
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.launch
@@ -35,11 +33,14 @@
 class FromDozingTransitionInteractor
 @Inject
 constructor(
+    override val transitionRepository: KeyguardTransitionRepository,
+    override val transitionInteractor: KeyguardTransitionInteractor,
     @Application private val scope: CoroutineScope,
     private val keyguardInteractor: KeyguardInteractor,
-    private val keyguardTransitionRepository: KeyguardTransitionRepository,
-    private val keyguardTransitionInteractor: KeyguardTransitionInteractor,
-) : TransitionInteractor(FromDozingTransitionInteractor::class.simpleName!!) {
+) :
+    TransitionInteractor(
+        fromState = KeyguardState.DOZING,
+    ) {
 
     override fun start() {
         listenForDozingToLockscreen()
@@ -49,20 +50,13 @@
     private fun listenForDozingToLockscreen() {
         scope.launch {
             keyguardInteractor.wakefulnessModel
-                .sample(keyguardTransitionInteractor.startedKeyguardTransitionStep, ::Pair)
+                .sample(transitionInteractor.startedKeyguardTransitionStep, ::Pair)
                 .collect { (wakefulnessModel, lastStartedTransition) ->
                     if (
                         wakefulnessModel.isStartingToWakeOrAwake() &&
                             lastStartedTransition.to == KeyguardState.DOZING
                     ) {
-                        keyguardTransitionRepository.startTransition(
-                            TransitionInfo(
-                                name,
-                                KeyguardState.DOZING,
-                                KeyguardState.LOCKSCREEN,
-                                getAnimator(),
-                            )
-                        )
+                        startTransitionTo(KeyguardState.LOCKSCREEN)
                     }
                 }
         }
@@ -71,29 +65,22 @@
     private fun listenForDozingToGone() {
         scope.launch {
             keyguardInteractor.biometricUnlockState
-                .sample(keyguardTransitionInteractor.startedKeyguardTransitionStep, ::Pair)
+                .sample(transitionInteractor.startedKeyguardTransitionStep, ::Pair)
                 .collect { (biometricUnlockState, lastStartedTransition) ->
                     if (
                         lastStartedTransition.to == KeyguardState.DOZING &&
                             isWakeAndUnlock(biometricUnlockState)
                     ) {
-                        keyguardTransitionRepository.startTransition(
-                            TransitionInfo(
-                                name,
-                                KeyguardState.DOZING,
-                                KeyguardState.GONE,
-                                getAnimator(),
-                            )
-                        )
+                        startTransitionTo(KeyguardState.GONE)
                     }
                 }
         }
     }
 
-    private fun getAnimator(duration: Duration = DEFAULT_DURATION): ValueAnimator {
+    override fun getDefaultAnimatorForTransitionsToState(toState: KeyguardState): ValueAnimator {
         return ValueAnimator().apply {
-            setInterpolator(Interpolators.LINEAR)
-            setDuration(duration.inWholeMilliseconds)
+            interpolator = Interpolators.LINEAR
+            duration = DEFAULT_DURATION.inWholeMilliseconds
         }
     }
 
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 ccf4bc1..98d7434 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
@@ -24,11 +24,9 @@
 import com.android.systemui.keyguard.shared.model.BiometricUnlockModel
 import com.android.systemui.keyguard.shared.model.DozeStateModel
 import com.android.systemui.keyguard.shared.model.KeyguardState
-import com.android.systemui.keyguard.shared.model.TransitionInfo
 import com.android.systemui.util.kotlin.Utils.Companion.toTriple
 import com.android.systemui.util.kotlin.sample
 import javax.inject.Inject
-import kotlin.time.Duration
 import kotlin.time.Duration.Companion.milliseconds
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.delay
@@ -40,11 +38,14 @@
 class FromDreamingTransitionInteractor
 @Inject
 constructor(
+    override val transitionRepository: KeyguardTransitionRepository,
+    override val transitionInteractor: KeyguardTransitionInteractor,
     @Application private val scope: CoroutineScope,
     private val keyguardInteractor: KeyguardInteractor,
-    private val keyguardTransitionRepository: KeyguardTransitionRepository,
-    private val keyguardTransitionInteractor: KeyguardTransitionInteractor,
-) : TransitionInteractor(FromDreamingTransitionInteractor::class.simpleName!!) {
+) :
+    TransitionInteractor(
+        fromState = KeyguardState.DREAMING,
+    ) {
 
     override fun start() {
         listenForDreamingToOccluded()
@@ -54,15 +55,8 @@
 
     fun startToLockscreenTransition() {
         scope.launch {
-            if (keyguardTransitionInteractor.startedKeyguardState.value == KeyguardState.DREAMING) {
-                keyguardTransitionRepository.startTransition(
-                    TransitionInfo(
-                        name,
-                        KeyguardState.DREAMING,
-                        KeyguardState.LOCKSCREEN,
-                        getAnimator(TO_LOCKSCREEN_DURATION),
-                    )
-                )
+            if (transitionInteractor.startedKeyguardState.value == KeyguardState.DREAMING) {
+                startTransitionTo(KeyguardState.LOCKSCREEN)
             }
         }
     }
@@ -76,7 +70,7 @@
                 .sample(
                     combine(
                         keyguardInteractor.isKeyguardOccluded,
-                        keyguardTransitionInteractor.startedKeyguardTransitionStep,
+                        transitionInteractor.startedKeyguardTransitionStep,
                         ::Pair,
                     ),
                     ::toTriple
@@ -92,14 +86,7 @@
                         // action. There's no great signal to determine when the dream is ending
                         // and a transition to OCCLUDED is beginning directly. For now, the solution
                         // is DREAMING->LOCKSCREEN->OCCLUDED
-                        keyguardTransitionRepository.startTransition(
-                            TransitionInfo(
-                                name,
-                                lastStartedTransition.to,
-                                KeyguardState.OCCLUDED,
-                                getAnimator(),
-                            )
-                        )
+                        startTransitionTo(KeyguardState.OCCLUDED)
                     }
                 }
         }
@@ -109,14 +96,7 @@
         scope.launch {
             keyguardInteractor.biometricUnlockState.collect { biometricUnlockState ->
                 if (biometricUnlockState == BiometricUnlockModel.WAKE_AND_UNLOCK_FROM_DREAM) {
-                    keyguardTransitionRepository.startTransition(
-                        TransitionInfo(
-                            name,
-                            KeyguardState.DREAMING,
-                            KeyguardState.GONE,
-                            getAnimator(),
-                        )
-                    )
+                    startTransitionTo(KeyguardState.GONE)
                 }
             }
         }
@@ -126,7 +106,7 @@
         scope.launch {
             combine(
                     keyguardInteractor.dozeTransitionModel,
-                    keyguardTransitionInteractor.finishedKeyguardState,
+                    transitionInteractor.finishedKeyguardState,
                     ::Pair
                 )
                 .collect { (dozeTransitionModel, keyguardState) ->
@@ -134,23 +114,18 @@
                         dozeTransitionModel.to == DozeStateModel.DOZE &&
                             keyguardState == KeyguardState.DREAMING
                     ) {
-                        keyguardTransitionRepository.startTransition(
-                            TransitionInfo(
-                                name,
-                                KeyguardState.DREAMING,
-                                KeyguardState.DOZING,
-                                getAnimator(),
-                            )
-                        )
+                        startTransitionTo(KeyguardState.DOZING)
                     }
                 }
         }
     }
 
-    private fun getAnimator(duration: Duration = DEFAULT_DURATION): ValueAnimator {
+    override fun getDefaultAnimatorForTransitionsToState(toState: KeyguardState): ValueAnimator {
         return ValueAnimator().apply {
-            setInterpolator(Interpolators.LINEAR)
-            setDuration(duration.inWholeMilliseconds)
+            interpolator = Interpolators.LINEAR
+            duration =
+                if (toState == KeyguardState.LOCKSCREEN) TO_LOCKSCREEN_DURATION.inWholeMilliseconds
+                else DEFAULT_DURATION.inWholeMilliseconds
         }
     }
 
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 cfcb654..f82633f 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
@@ -22,12 +22,10 @@
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
 import com.android.systemui.keyguard.shared.model.KeyguardState
-import com.android.systemui.keyguard.shared.model.TransitionInfo
 import com.android.systemui.keyguard.shared.model.WakefulnessState
 import com.android.systemui.util.kotlin.Utils.Companion.toTriple
 import com.android.systemui.util.kotlin.sample
 import javax.inject.Inject
-import kotlin.time.Duration
 import kotlin.time.Duration.Companion.milliseconds
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.flow.combine
@@ -37,11 +35,14 @@
 class FromGoneTransitionInteractor
 @Inject
 constructor(
+    override val transitionRepository: KeyguardTransitionRepository,
+    override val transitionInteractor: KeyguardTransitionInteractor,
     @Application private val scope: CoroutineScope,
     private val keyguardInteractor: KeyguardInteractor,
-    private val keyguardTransitionRepository: KeyguardTransitionRepository,
-    private val keyguardTransitionInteractor: KeyguardTransitionInteractor,
-) : TransitionInteractor(FromGoneTransitionInteractor::class.simpleName!!) {
+) :
+    TransitionInteractor(
+        fromState = KeyguardState.GONE,
+    ) {
 
     override fun start() {
         listenForGoneToAodOrDozing()
@@ -53,17 +54,10 @@
     private fun listenForGoneToLockscreen() {
         scope.launch {
             keyguardInteractor.isKeyguardShowing
-                .sample(keyguardTransitionInteractor.startedKeyguardTransitionStep, ::Pair)
+                .sample(transitionInteractor.startedKeyguardTransitionStep, ::Pair)
                 .collect { (isKeyguardShowing, lastStartedStep) ->
                     if (isKeyguardShowing && lastStartedStep.to == KeyguardState.GONE) {
-                        keyguardTransitionRepository.startTransition(
-                            TransitionInfo(
-                                name,
-                                KeyguardState.GONE,
-                                KeyguardState.LOCKSCREEN,
-                                getAnimator(),
-                            )
-                        )
+                        startTransitionTo(KeyguardState.LOCKSCREEN)
                     }
                 }
         }
@@ -72,17 +66,10 @@
     private fun listenForGoneToDreaming() {
         scope.launch {
             keyguardInteractor.isAbleToDream
-                .sample(keyguardTransitionInteractor.startedKeyguardTransitionStep, ::Pair)
+                .sample(transitionInteractor.startedKeyguardTransitionStep, ::Pair)
                 .collect { (isAbleToDream, lastStartedStep) ->
                     if (isAbleToDream && lastStartedStep.to == KeyguardState.GONE) {
-                        keyguardTransitionRepository.startTransition(
-                            TransitionInfo(
-                                name,
-                                KeyguardState.GONE,
-                                KeyguardState.DREAMING,
-                                getAnimator(TO_DREAMING_DURATION),
-                            )
-                        )
+                        startTransitionTo(KeyguardState.DREAMING)
                     }
                 }
         }
@@ -93,7 +80,7 @@
             keyguardInteractor.wakefulnessModel
                 .sample(
                     combine(
-                        keyguardTransitionInteractor.startedKeyguardTransitionStep,
+                        transitionInteractor.startedKeyguardTransitionStep,
                         keyguardInteractor.isAodAvailable,
                         ::Pair
                     ),
@@ -104,30 +91,24 @@
                         lastStartedStep.to == KeyguardState.GONE &&
                             wakefulnessState.state == WakefulnessState.STARTING_TO_SLEEP
                     ) {
-                        keyguardTransitionRepository.startTransition(
-                            TransitionInfo(
-                                name,
-                                KeyguardState.GONE,
-                                if (isAodAvailable) {
-                                    KeyguardState.AOD
-                                } else {
-                                    KeyguardState.DOZING
-                                },
-                                getAnimator(),
-                            )
+                        startTransitionTo(
+                            if (isAodAvailable) KeyguardState.AOD else KeyguardState.DOZING
                         )
                     }
                 }
         }
     }
 
-    private fun getAnimator(duration: Duration = DEFAULT_DURATION): ValueAnimator {
+    override fun getDefaultAnimatorForTransitionsToState(toState: KeyguardState): ValueAnimator {
         return ValueAnimator().apply {
-            setInterpolator(Interpolators.LINEAR)
-            setDuration(duration.inWholeMilliseconds)
+            interpolator = Interpolators.LINEAR
+            duration =
+                when (toState) {
+                    KeyguardState.DREAMING -> TO_DREAMING_DURATION
+                    else -> DEFAULT_DURATION
+                }.inWholeMilliseconds
         }
     }
-
     companion object {
         private val DEFAULT_DURATION = 500.milliseconds
         val TO_DREAMING_DURATION = 933.milliseconds
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 b5e289f..b796334 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
@@ -32,7 +32,6 @@
 import com.android.systemui.util.kotlin.sample
 import java.util.UUID
 import javax.inject.Inject
-import kotlin.time.Duration
 import kotlin.time.Duration.Companion.milliseconds
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.flow.combine
@@ -42,12 +41,15 @@
 class FromLockscreenTransitionInteractor
 @Inject
 constructor(
+    override val transitionRepository: KeyguardTransitionRepository,
+    override val transitionInteractor: KeyguardTransitionInteractor,
     @Application private val scope: CoroutineScope,
     private val keyguardInteractor: KeyguardInteractor,
     private val shadeRepository: ShadeRepository,
-    private val keyguardTransitionInteractor: KeyguardTransitionInteractor,
-    private val keyguardTransitionRepository: KeyguardTransitionRepository,
-) : TransitionInteractor(FromLockscreenTransitionInteractor::class.simpleName!!) {
+) :
+    TransitionInteractor(
+        fromState = KeyguardState.LOCKSCREEN,
+    ) {
 
     override fun start() {
         listenForLockscreenToGone()
@@ -66,8 +68,8 @@
             keyguardInteractor.isAbleToDream
                 .sample(
                     combine(
-                        keyguardTransitionInteractor.startedKeyguardTransitionStep,
-                        keyguardTransitionInteractor.finishedKeyguardState,
+                        transitionInteractor.startedKeyguardTransitionStep,
+                        transitionInteractor.finishedKeyguardState,
                         ::Pair
                     ),
                     ::toTriple
@@ -78,14 +80,7 @@
                         lastStartedTransition.to == KeyguardState.LOCKSCREEN &&
                             !invalidFromStates.contains(lastStartedTransition.from)
                     if (isAbleToDream && (isOnLockscreen || isTransitionInterruptible)) {
-                        keyguardTransitionRepository.startTransition(
-                            TransitionInfo(
-                                name,
-                                KeyguardState.LOCKSCREEN,
-                                KeyguardState.DREAMING,
-                                getAnimator(TO_DREAMING_DURATION),
-                            )
-                        )
+                        startTransitionTo(KeyguardState.DREAMING)
                     }
                 }
         }
@@ -94,20 +89,13 @@
     private fun listenForLockscreenToPrimaryBouncer() {
         scope.launch {
             keyguardInteractor.primaryBouncerShowing
-                .sample(keyguardTransitionInteractor.startedKeyguardTransitionStep, ::Pair)
+                .sample(transitionInteractor.startedKeyguardTransitionStep, ::Pair)
                 .collect { pair ->
                     val (isBouncerShowing, lastStartedTransitionStep) = pair
                     if (
                         isBouncerShowing && lastStartedTransitionStep.to == KeyguardState.LOCKSCREEN
                     ) {
-                        keyguardTransitionRepository.startTransition(
-                            TransitionInfo(
-                                ownerName = name,
-                                from = KeyguardState.LOCKSCREEN,
-                                to = KeyguardState.PRIMARY_BOUNCER,
-                                animator = getAnimator(),
-                            )
-                        )
+                        startTransitionTo(KeyguardState.PRIMARY_BOUNCER)
                     }
                 }
         }
@@ -116,21 +104,14 @@
     private fun listenForLockscreenToAlternateBouncer() {
         scope.launch {
             keyguardInteractor.alternateBouncerShowing
-                .sample(keyguardTransitionInteractor.startedKeyguardTransitionStep, ::Pair)
+                .sample(transitionInteractor.startedKeyguardTransitionStep, ::Pair)
                 .collect { pair ->
                     val (isAlternateBouncerShowing, lastStartedTransitionStep) = pair
                     if (
                         isAlternateBouncerShowing &&
                             lastStartedTransitionStep.to == KeyguardState.LOCKSCREEN
                     ) {
-                        keyguardTransitionRepository.startTransition(
-                            TransitionInfo(
-                                ownerName = name,
-                                from = KeyguardState.LOCKSCREEN,
-                                to = KeyguardState.ALTERNATE_BOUNCER,
-                                animator = getAnimator(),
-                            )
-                        )
+                        startTransitionTo(KeyguardState.ALTERNATE_BOUNCER)
                     }
                 }
         }
@@ -143,7 +124,7 @@
             shadeRepository.shadeModel
                 .sample(
                     combine(
-                        keyguardTransitionInteractor.startedKeyguardTransitionStep,
+                        transitionInteractor.startedKeyguardTransitionStep,
                         keyguardInteractor.statusBarState,
                         keyguardInteractor.isKeyguardUnlocked,
                         ::Triple
@@ -164,7 +145,7 @@
                                 } else {
                                     TransitionState.RUNNING
                                 }
-                            keyguardTransitionRepository.updateTransition(
+                            transitionRepository.updateTransition(
                                 id,
                                 1f - shadeModel.expansionAmount,
                                 nextState,
@@ -178,13 +159,17 @@
                             }
 
                             // If canceled, just put the state back
+                            // TODO: This logic should happen in FromPrimaryBouncerInteractor.
                             if (nextState == TransitionState.CANCELED) {
-                                keyguardTransitionRepository.startTransition(
+                                transitionRepository.startTransition(
                                     TransitionInfo(
                                         ownerName = name,
                                         from = KeyguardState.PRIMARY_BOUNCER,
                                         to = KeyguardState.LOCKSCREEN,
-                                        animator = getAnimator(0.milliseconds)
+                                        animator =
+                                            getDefaultAnimatorForTransitionsToState(KeyguardState.LOCKSCREEN).apply {
+                                                duration = 0
+                                            }
                                     )
                                 )
                             }
@@ -198,15 +183,7 @@
                                 !isKeyguardUnlocked &&
                                 statusBarState == KEYGUARD
                         ) {
-                            transitionId =
-                                keyguardTransitionRepository.startTransition(
-                                    TransitionInfo(
-                                        ownerName = name,
-                                        from = KeyguardState.LOCKSCREEN,
-                                        to = KeyguardState.PRIMARY_BOUNCER,
-                                        animator = null,
-                                    )
-                                )
+                            transitionId = startTransitionTo(KeyguardState.PRIMARY_BOUNCER)
                         }
                     }
                 }
@@ -216,18 +193,11 @@
     private fun listenForLockscreenToGone() {
         scope.launch {
             keyguardInteractor.isKeyguardGoingAway
-                .sample(keyguardTransitionInteractor.startedKeyguardTransitionStep, ::Pair)
+                .sample(transitionInteractor.startedKeyguardTransitionStep, ::Pair)
                 .collect { pair ->
                     val (isKeyguardGoingAway, lastStartedStep) = pair
                     if (isKeyguardGoingAway && lastStartedStep.to == KeyguardState.LOCKSCREEN) {
-                        keyguardTransitionRepository.startTransition(
-                            TransitionInfo(
-                                name,
-                                KeyguardState.LOCKSCREEN,
-                                KeyguardState.GONE,
-                                getAnimator(),
-                            )
-                        )
+                        startTransitionTo(KeyguardState.GONE)
                     }
                 }
         }
@@ -238,7 +208,7 @@
             keyguardInteractor.isKeyguardOccluded
                 .sample(
                     combine(
-                        keyguardTransitionInteractor.finishedKeyguardState,
+                        transitionInteractor.finishedKeyguardState,
                         keyguardInteractor.isDreaming,
                         ::Pair
                     ),
@@ -246,14 +216,7 @@
                 )
                 .collect { (isOccluded, keyguardState, isDreaming) ->
                     if (isOccluded && !isDreaming && keyguardState == KeyguardState.LOCKSCREEN) {
-                        keyguardTransitionRepository.startTransition(
-                            TransitionInfo(
-                                name,
-                                keyguardState,
-                                KeyguardState.OCCLUDED,
-                                getAnimator(TO_OCCLUDED_DURATION),
-                            )
-                        )
+                        startTransitionTo(KeyguardState.OCCLUDED)
                     }
                 }
         }
@@ -263,7 +226,7 @@
     private fun listenForLockscreenToCamera() {
         scope.launch {
             keyguardInteractor.onCameraLaunchDetected
-                .sample(keyguardTransitionInteractor.startedKeyguardTransitionStep, ::Pair)
+                .sample(transitionInteractor.startedKeyguardTransitionStep, ::Pair)
                 .collect { (_, lastStartedStep) ->
                     // DREAMING/AOD/OFF may trigger on the first power button push, so include this
                     // state in order to cancel and correct the transition
@@ -274,14 +237,7 @@
                             lastStartedStep.to == KeyguardState.AOD ||
                             lastStartedStep.to == KeyguardState.OFF
                     ) {
-                        keyguardTransitionRepository.startTransition(
-                            TransitionInfo(
-                                name,
-                                KeyguardState.LOCKSCREEN,
-                                KeyguardState.OCCLUDED,
-                                getAnimator(TO_OCCLUDED_DURATION),
-                            )
-                        )
+                        startTransitionTo(KeyguardState.OCCLUDED)
                     }
                 }
         }
@@ -292,7 +248,7 @@
             keyguardInteractor.wakefulnessModel
                 .sample(
                     combine(
-                        keyguardTransitionInteractor.startedKeyguardTransitionStep,
+                        transitionInteractor.startedKeyguardTransitionStep,
                         keyguardInteractor.isAodAvailable,
                         ::Pair
                     ),
@@ -303,27 +259,23 @@
                         lastStartedStep.to == KeyguardState.LOCKSCREEN &&
                             wakefulnessState.state == WakefulnessState.STARTING_TO_SLEEP
                     ) {
-                        keyguardTransitionRepository.startTransition(
-                            TransitionInfo(
-                                name,
-                                KeyguardState.LOCKSCREEN,
-                                if (isAodAvailable) {
-                                    KeyguardState.AOD
-                                } else {
-                                    KeyguardState.DOZING
-                                },
-                                getAnimator(),
-                            )
+                        startTransitionTo(
+                            if (isAodAvailable) KeyguardState.AOD else KeyguardState.DOZING
                         )
                     }
                 }
         }
     }
 
-    private fun getAnimator(duration: Duration = DEFAULT_DURATION): ValueAnimator {
+    override fun getDefaultAnimatorForTransitionsToState(toState: KeyguardState): ValueAnimator {
         return ValueAnimator().apply {
-            setInterpolator(Interpolators.LINEAR)
-            setDuration(duration.inWholeMilliseconds)
+            interpolator = Interpolators.LINEAR
+            duration =
+                when (toState) {
+                    KeyguardState.DREAMING -> TO_DREAMING_DURATION
+                    KeyguardState.OCCLUDED -> TO_OCCLUDED_DURATION
+                    else -> DEFAULT_DURATION
+                }.inWholeMilliseconds
         }
     }
 
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 b0dbc59..a8147d0 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
@@ -22,12 +22,10 @@
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
 import com.android.systemui.keyguard.shared.model.KeyguardState
-import com.android.systemui.keyguard.shared.model.TransitionInfo
 import com.android.systemui.keyguard.shared.model.WakefulnessState
 import com.android.systemui.util.kotlin.Utils.Companion.toTriple
 import com.android.systemui.util.kotlin.sample
 import javax.inject.Inject
-import kotlin.time.Duration
 import kotlin.time.Duration.Companion.milliseconds
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.flow.combine
@@ -37,11 +35,14 @@
 class FromOccludedTransitionInteractor
 @Inject
 constructor(
+    override val transitionRepository: KeyguardTransitionRepository,
+    override val transitionInteractor: KeyguardTransitionInteractor,
     @Application private val scope: CoroutineScope,
     private val keyguardInteractor: KeyguardInteractor,
-    private val keyguardTransitionRepository: KeyguardTransitionRepository,
-    private val keyguardTransitionInteractor: KeyguardTransitionInteractor,
-) : TransitionInteractor(FromOccludedTransitionInteractor::class.simpleName!!) {
+) :
+    TransitionInteractor(
+        fromState = KeyguardState.OCCLUDED,
+    ) {
 
     override fun start() {
         listenForOccludedToLockscreen()
@@ -54,18 +55,11 @@
     private fun listenForOccludedToDreaming() {
         scope.launch {
             keyguardInteractor.isAbleToDream
-                .sample(keyguardTransitionInteractor.finishedKeyguardState, ::Pair)
+                .sample(transitionInteractor.finishedKeyguardState, ::Pair)
                 .collect { pair ->
                     val (isAbleToDream, keyguardState) = pair
                     if (isAbleToDream && keyguardState == KeyguardState.OCCLUDED) {
-                        keyguardTransitionRepository.startTransition(
-                            TransitionInfo(
-                                name,
-                                KeyguardState.OCCLUDED,
-                                KeyguardState.DREAMING,
-                                getAnimator(),
-                            )
-                        )
+                        startTransitionTo(KeyguardState.DREAMING)
                     }
                 }
         }
@@ -77,7 +71,7 @@
                 .sample(
                     combine(
                         keyguardInteractor.isKeyguardShowing,
-                        keyguardTransitionInteractor.startedKeyguardTransitionStep,
+                        transitionInteractor.startedKeyguardTransitionStep,
                         ::Pair
                     ),
                     ::toTriple
@@ -90,14 +84,7 @@
                             isShowing &&
                             lastStartedKeyguardState.to == KeyguardState.OCCLUDED
                     ) {
-                        keyguardTransitionRepository.startTransition(
-                            TransitionInfo(
-                                name,
-                                KeyguardState.OCCLUDED,
-                                KeyguardState.LOCKSCREEN,
-                                getAnimator(TO_LOCKSCREEN_DURATION),
-                            )
-                        )
+                        startTransitionTo(KeyguardState.LOCKSCREEN)
                     }
                 }
         }
@@ -109,7 +96,7 @@
                 .sample(
                     combine(
                         keyguardInteractor.isKeyguardShowing,
-                        keyguardTransitionInteractor.startedKeyguardTransitionStep,
+                        transitionInteractor.startedKeyguardTransitionStep,
                         ::Pair
                     ),
                     ::toTriple
@@ -122,14 +109,7 @@
                             !isShowing &&
                             lastStartedKeyguardState.to == KeyguardState.OCCLUDED
                     ) {
-                        keyguardTransitionRepository.startTransition(
-                            TransitionInfo(
-                                name,
-                                KeyguardState.OCCLUDED,
-                                KeyguardState.GONE,
-                                getAnimator(),
-                            )
-                        )
+                        startTransitionTo(KeyguardState.GONE)
                     }
                 }
         }
@@ -140,7 +120,7 @@
             keyguardInteractor.wakefulnessModel
                 .sample(
                     combine(
-                        keyguardTransitionInteractor.startedKeyguardTransitionStep,
+                        transitionInteractor.startedKeyguardTransitionStep,
                         keyguardInteractor.isAodAvailable,
                         ::Pair
                     ),
@@ -151,17 +131,8 @@
                         lastStartedStep.to == KeyguardState.OCCLUDED &&
                             wakefulnessState.state == WakefulnessState.STARTING_TO_SLEEP
                     ) {
-                        keyguardTransitionRepository.startTransition(
-                            TransitionInfo(
-                                name,
-                                KeyguardState.OCCLUDED,
-                                if (isAodAvailable) {
-                                    KeyguardState.AOD
-                                } else {
-                                    KeyguardState.DOZING
-                                },
-                                getAnimator(),
-                            )
+                        startTransitionTo(
+                            if (isAodAvailable) KeyguardState.AOD else KeyguardState.DOZING
                         )
                     }
                 }
@@ -171,29 +142,26 @@
     private fun listenForOccludedToAlternateBouncer() {
         scope.launch {
             keyguardInteractor.alternateBouncerShowing
-                .sample(keyguardTransitionInteractor.startedKeyguardTransitionStep, ::Pair)
+                .sample(transitionInteractor.startedKeyguardTransitionStep, ::Pair)
                 .collect { (isAlternateBouncerShowing, lastStartedTransitionStep) ->
                     if (
                         isAlternateBouncerShowing &&
                             lastStartedTransitionStep.to == KeyguardState.OCCLUDED
                     ) {
-                        keyguardTransitionRepository.startTransition(
-                            TransitionInfo(
-                                ownerName = name,
-                                from = KeyguardState.OCCLUDED,
-                                to = KeyguardState.ALTERNATE_BOUNCER,
-                                animator = getAnimator(),
-                            )
-                        )
+                        startTransitionTo(KeyguardState.ALTERNATE_BOUNCER)
                     }
                 }
         }
     }
 
-    private fun getAnimator(duration: Duration = DEFAULT_DURATION): ValueAnimator {
+    override fun getDefaultAnimatorForTransitionsToState(toState: KeyguardState): ValueAnimator {
         return ValueAnimator().apply {
-            setInterpolator(Interpolators.LINEAR)
-            setDuration(duration.inWholeMilliseconds)
+            interpolator = Interpolators.LINEAR
+            duration =
+                when (toState) {
+                    KeyguardState.LOCKSCREEN -> TO_LOCKSCREEN_DURATION
+                    else -> DEFAULT_DURATION
+                }.inWholeMilliseconds
         }
     }
 
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 da09e1f..e1754f5 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
@@ -25,12 +25,10 @@
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
 import com.android.systemui.keyguard.shared.model.KeyguardState
-import com.android.systemui.keyguard.shared.model.TransitionInfo
 import com.android.systemui.keyguard.shared.model.WakefulnessState
 import com.android.systemui.util.kotlin.Utils.Companion.toQuad
 import com.android.systemui.util.kotlin.sample
 import javax.inject.Inject
-import kotlin.time.Duration
 import kotlin.time.Duration.Companion.milliseconds
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.flow.combine
@@ -40,12 +38,15 @@
 class FromPrimaryBouncerTransitionInteractor
 @Inject
 constructor(
+    override val transitionRepository: KeyguardTransitionRepository,
+    override val transitionInteractor: KeyguardTransitionInteractor,
     @Application private val scope: CoroutineScope,
     private val keyguardInteractor: KeyguardInteractor,
-    private val keyguardTransitionRepository: KeyguardTransitionRepository,
-    private val keyguardTransitionInteractor: KeyguardTransitionInteractor,
     private val keyguardSecurityModel: KeyguardSecurityModel,
-) : TransitionInteractor(FromPrimaryBouncerTransitionInteractor::class.simpleName!!) {
+) :
+    TransitionInteractor(
+        fromState = KeyguardState.PRIMARY_BOUNCER,
+    ) {
 
     override fun start() {
         listenForPrimaryBouncerToGone()
@@ -59,7 +60,7 @@
                 .sample(
                     combine(
                         keyguardInteractor.wakefulnessModel,
-                        keyguardTransitionInteractor.startedKeyguardTransitionStep,
+                        transitionInteractor.startedKeyguardTransitionStep,
                         keyguardInteractor.isKeyguardOccluded,
                         ::Triple
                     ),
@@ -73,20 +74,8 @@
                             (wakefulnessState.state == WakefulnessState.AWAKE ||
                                 wakefulnessState.state == WakefulnessState.STARTING_TO_WAKE)
                     ) {
-                        val to =
-                            if (occluded) {
-                                KeyguardState.OCCLUDED
-                            } else {
-                                KeyguardState.LOCKSCREEN
-                            }
-
-                        keyguardTransitionRepository.startTransition(
-                            TransitionInfo(
-                                ownerName = name,
-                                from = KeyguardState.PRIMARY_BOUNCER,
-                                to = to,
-                                animator = getAnimator(),
-                            )
+                        startTransitionTo(
+                            if (occluded) KeyguardState.OCCLUDED else KeyguardState.LOCKSCREEN
                         )
                     }
                 }
@@ -99,7 +88,7 @@
                 .sample(
                     combine(
                         keyguardInteractor.wakefulnessModel,
-                        keyguardTransitionInteractor.startedKeyguardTransitionStep,
+                        transitionInteractor.startedKeyguardTransitionStep,
                         keyguardInteractor.isAodAvailable,
                         ::Triple
                     ),
@@ -114,20 +103,8 @@
                             (wakefulnessState.state == WakefulnessState.STARTING_TO_SLEEP ||
                                 wakefulnessState.state == WakefulnessState.ASLEEP)
                     ) {
-                        val to =
-                            if (isAodAvailable) {
-                                KeyguardState.AOD
-                            } else {
-                                KeyguardState.DOZING
-                            }
-
-                        keyguardTransitionRepository.startTransition(
-                            TransitionInfo(
-                                ownerName = name,
-                                from = KeyguardState.PRIMARY_BOUNCER,
-                                to = to,
-                                animator = getAnimator(),
-                            )
+                        startTransitionTo(
+                            if (isAodAvailable) KeyguardState.AOD else KeyguardState.DOZING
                         )
                     }
                 }
@@ -137,7 +114,7 @@
     private fun listenForPrimaryBouncerToGone() {
         scope.launch {
             keyguardInteractor.isKeyguardGoingAway
-                .sample(keyguardTransitionInteractor.startedKeyguardTransitionStep, ::Pair)
+                .sample(transitionInteractor.startedKeyguardTransitionStep, ::Pair)
                 .collect { (isKeyguardGoingAway, lastStartedTransitionStep) ->
                     if (
                         isKeyguardGoingAway &&
@@ -154,24 +131,24 @@
                             } else {
                                 TO_GONE_DURATION
                             }
-                        keyguardTransitionRepository.startTransition(
-                            TransitionInfo(
-                                ownerName = name,
-                                from = KeyguardState.PRIMARY_BOUNCER,
-                                to = KeyguardState.GONE,
-                                animator = getAnimator(duration),
-                            ),
-                            resetIfCanceled = true,
+
+                        startTransitionTo(
+                            toState = KeyguardState.GONE,
+                            animator =
+                                getDefaultAnimatorForTransitionsToState(KeyguardState.GONE).apply {
+                                    this.duration = duration.inWholeMilliseconds
+                                },
+                            resetIfCancelled = true
                         )
                     }
                 }
         }
     }
 
-    private fun getAnimator(duration: Duration = DEFAULT_DURATION): ValueAnimator {
+    override fun getDefaultAnimatorForTransitionsToState(toState: KeyguardState): ValueAnimator {
         return ValueAnimator().apply {
-            setInterpolator(Interpolators.LINEAR)
-            setDuration(duration.inWholeMilliseconds)
+            interpolator = Interpolators.LINEAR
+            duration = DEFAULT_DURATION.inWholeMilliseconds
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionAuditLogger.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionAuditLogger.kt
index 7c5641f..4f7abd4 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionAuditLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionAuditLogger.kt
@@ -19,7 +19,7 @@
 import com.android.keyguard.logging.KeyguardLogger
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
-import com.android.systemui.log.LogLevel.VERBOSE
+import com.android.systemui.log.core.LogLevel.VERBOSE
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.launch
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt
index 42f12f8..df7c79f 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt
@@ -47,6 +47,8 @@
     private val repository: KeyguardTransitionRepository,
     @Application val scope: CoroutineScope,
 ) {
+    private val TAG = this::class.simpleName
+
     /** (any)->GONE transition information */
     val anyStateToGoneTransition: Flow<TransitionStep> =
         repository.transitions.filter { step -> step.to == KeyguardState.GONE }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LockscreenSceneInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LockscreenSceneInteractor.kt
index d0bc25f..c8f7efb 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LockscreenSceneInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LockscreenSceneInteractor.kt
@@ -23,7 +23,6 @@
 import com.android.systemui.scene.domain.interactor.SceneInteractor
 import com.android.systemui.scene.shared.model.SceneKey
 import com.android.systemui.scene.shared.model.SceneModel
-import com.android.systemui.util.kotlin.pairwise
 import dagger.assisted.Assisted
 import dagger.assisted.AssistedFactory
 import dagger.assisted.AssistedInject
@@ -61,23 +60,16 @@
 
     /** Whether it's currently possible to swipe up to dismiss the lockscreen. */
     val isSwipeToDismissEnabled: StateFlow<Boolean> =
-        combine(
-                authenticationInteractor.isUnlocked,
-                authenticationInteractor.authenticationMethod,
-            ) { isUnlocked, authMethod ->
-                isSwipeToUnlockEnabled(
-                    isUnlocked = isUnlocked,
-                    authMethod = authMethod,
-                )
+        authenticationInteractor.isUnlocked
+            .map { isUnlocked ->
+                !isUnlocked &&
+                    authenticationInteractor.getAuthenticationMethod() is
+                        AuthenticationMethodModel.Swipe
             }
             .stateIn(
                 scope = applicationScope,
                 started = SharingStarted.WhileSubscribed(),
-                initialValue =
-                    isSwipeToUnlockEnabled(
-                        isUnlocked = authenticationInteractor.isUnlocked.value,
-                        authMethod = authenticationInteractor.authenticationMethod.value,
-                    ),
+                initialValue = false,
             )
 
     init {
@@ -118,51 +110,6 @@
                     }
                 }
         }
-
-        // SWIPE TO DISMISS Lockscreen.
-        //
-        // If switched from the lockscreen to the gone scene and the auth method was a swipe,
-        // unlocks the device.
-        applicationScope.launch {
-            combine(
-                    authenticationInteractor.authenticationMethod,
-                    sceneInteractor.currentScene(containerName).pairwise(),
-                    ::Pair,
-                )
-                .collect { (authMethod, scenes) ->
-                    val (previousScene, currentScene) = scenes
-                    if (
-                        authMethod is AuthenticationMethodModel.Swipe &&
-                            previousScene.key == SceneKey.Lockscreen &&
-                            currentScene.key == SceneKey.Gone
-                    ) {
-                        authenticationInteractor.unlockDevice()
-                    }
-                }
-        }
-
-        // DISMISS Lockscreen IF AUTH METHOD IS REMOVED.
-        //
-        // If the auth method becomes None while on the lockscreen scene, dismisses the lock
-        // screen.
-        applicationScope.launch {
-            combine(
-                    authenticationInteractor.authenticationMethod,
-                    sceneInteractor.currentScene(containerName),
-                    ::Pair,
-                )
-                .collect { (authMethod, scene) ->
-                    if (
-                        scene.key == SceneKey.Lockscreen &&
-                            authMethod == AuthenticationMethodModel.None
-                    ) {
-                        sceneInteractor.setCurrentScene(
-                            containerName = containerName,
-                            scene = SceneModel(SceneKey.Gone),
-                        )
-                    }
-                }
-        }
     }
 
     /** Attempts to dismiss the lockscreen. This will cause the bouncer to show, if needed. */
@@ -170,13 +117,6 @@
         bouncerInteractor.showOrUnlockDevice(containerName = containerName)
     }
 
-    private fun isSwipeToUnlockEnabled(
-        isUnlocked: Boolean,
-        authMethod: AuthenticationMethodModel,
-    ): Boolean {
-        return !isUnlocked && authMethod is AuthenticationMethodModel.Swipe
-    }
-
     @AssistedFactory
     interface Factory {
         fun create(
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/SystemUIKeyguardFaceAuthInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/SystemUIKeyguardFaceAuthInteractor.kt
index 8b749f0..d467225 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/SystemUIKeyguardFaceAuthInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/SystemUIKeyguardFaceAuthInteractor.kt
@@ -16,9 +16,12 @@
 
 package com.android.systemui.keyguard.domain.interactor
 
+import android.content.Context
+import android.hardware.biometrics.BiometricFaceConstants
 import com.android.keyguard.FaceAuthUiEvent
 import com.android.keyguard.KeyguardUpdateMonitor
 import com.android.systemui.CoreStartable
+import com.android.systemui.R
 import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor
 import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor
 import com.android.systemui.dagger.SysUISingleton
@@ -27,6 +30,8 @@
 import com.android.systemui.flags.FeatureFlags
 import com.android.systemui.flags.Flags
 import com.android.systemui.keyguard.data.repository.DeviceEntryFaceAuthRepository
+import com.android.systemui.keyguard.shared.model.AuthenticationStatus
+import com.android.systemui.keyguard.shared.model.ErrorAuthenticationStatus
 import com.android.systemui.keyguard.shared.model.TransitionState
 import com.android.systemui.log.FaceAuthenticationLogger
 import com.android.systemui.util.kotlin.pairwise
@@ -34,7 +39,9 @@
 import kotlinx.coroutines.CoroutineDispatcher
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.filter
+import kotlinx.coroutines.flow.filterNotNull
 import kotlinx.coroutines.flow.flowOn
 import kotlinx.coroutines.flow.launchIn
 import kotlinx.coroutines.flow.map
@@ -50,6 +57,7 @@
 class SystemUIKeyguardFaceAuthInteractor
 @Inject
 constructor(
+    private val context: Context,
     @Application private val applicationScope: CoroutineScope,
     @Main private val mainDispatcher: CoroutineDispatcher,
     private val repository: DeviceEntryFaceAuthRepository,
@@ -157,17 +165,28 @@
         repository.cancel()
     }
 
+    private val _authenticationStatusOverride = MutableStateFlow<AuthenticationStatus?>(null)
     /** Provide the status of face authentication */
-    override val authenticationStatus = repository.authenticationStatus
+    override val authenticationStatus =
+        merge(_authenticationStatusOverride.filterNotNull(), repository.authenticationStatus)
 
     /** Provide the status of face detection */
     override val detectionStatus = repository.detectionStatus
 
     private fun runFaceAuth(uiEvent: FaceAuthUiEvent, fallbackToDetect: Boolean) {
         if (featureFlags.isEnabled(Flags.FACE_AUTH_REFACTOR)) {
-            applicationScope.launch {
-                faceAuthenticationLogger.authRequested(uiEvent)
-                repository.authenticate(uiEvent, fallbackToDetection = fallbackToDetect)
+            if (repository.isLockedOut.value) {
+                _authenticationStatusOverride.value =
+                    ErrorAuthenticationStatus(
+                        BiometricFaceConstants.FACE_ERROR_LOCKOUT_PERMANENT,
+                        context.resources.getString(R.string.keyguard_face_unlock_unavailable)
+                    )
+            } else {
+                _authenticationStatusOverride.value = null
+                applicationScope.launch {
+                    faceAuthenticationLogger.authRequested(uiEvent)
+                    repository.authenticate(uiEvent, fallbackToDetection = fallbackToDetect)
+                }
             }
         } else {
             faceAuthenticationLogger.ignoredFaceAuthTrigger(
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/TransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/TransitionInteractor.kt
index b7dd1a5..ae6fc9e 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/TransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/TransitionInteractor.kt
@@ -15,6 +15,14 @@
  */
 
 package com.android.systemui.keyguard.domain.interactor
+
+import android.animation.ValueAnimator
+import android.util.Log
+import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
+import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.TransitionInfo
+import java.util.UUID
+
 /**
  * Each TransitionInteractor is responsible for determining under which conditions to notify
  * [KeyguardTransitionRepository] to signal a transition. When (and if) the transition occurs is
@@ -26,6 +34,50 @@
  * MUST list implementing classes in dagger module [StartKeyguardTransitionModule] and also in the
  * 'when' clause of [KeyguardTransitionCoreStartable]
  */
-sealed class TransitionInteractor(val name: String) {
+sealed class TransitionInteractor(
+    val fromState: KeyguardState,
+) {
+    val name = this::class.simpleName ?: "UnknownTransitionInteractor"
+
+    abstract val transitionRepository: KeyguardTransitionRepository
+    abstract val transitionInteractor: KeyguardTransitionInteractor
     abstract fun start()
+
+    fun startTransitionTo(
+            toState: KeyguardState,
+            animator: ValueAnimator? = getDefaultAnimatorForTransitionsToState(toState),
+            resetIfCancelled: Boolean = false
+    ): UUID? {
+        if (
+            fromState != transitionInteractor.startedKeyguardState.value &&
+                fromState != transitionInteractor.finishedKeyguardState.value
+        ) {
+            Log.e(
+                name,
+                "startTransition: We were asked to transition from " +
+                    "$fromState to $toState, however we last finished a transition to " +
+                    "${transitionInteractor.finishedKeyguardState.value}, " +
+                    "and last started a transition to " +
+                    "${transitionInteractor.startedKeyguardState.value}. " +
+                    "Ignoring startTransition, but this should never happen."
+            )
+            return null
+        }
+
+        return transitionRepository.startTransition(
+            TransitionInfo(
+                name,
+                fromState,
+                toState,
+                animator,
+            ),
+            resetIfCancelled
+        )
+    }
+
+    /**
+     * Returns a ValueAnimator to be used for transitions to [toState], if one is not explicitly
+     * passed to [startTransitionTo].
+     */
+    abstract fun getDefaultAnimatorForTransitionsToState(toState: KeyguardState): ValueAnimator?
 }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/FaceAuthenticationModels.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/FaceAuthenticationModels.kt
index 9e7dec4..b354cfd 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/FaceAuthenticationModels.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/FaceAuthenticationModels.kt
@@ -17,6 +17,7 @@
 package com.android.systemui.keyguard.shared.model
 
 import android.hardware.face.FaceManager
+import android.os.SystemClock.elapsedRealtime
 
 /**
  * Authentication status provided by
@@ -38,8 +39,12 @@
 object FailedAuthenticationStatus : AuthenticationStatus()
 
 /** Face authentication error message */
-data class ErrorAuthenticationStatus(val msgId: Int, val msg: String? = null) :
-    AuthenticationStatus() {
+data class ErrorAuthenticationStatus(
+    val msgId: Int,
+    val msg: String? = null,
+    // present to break equality check if the same error occurs repeatedly.
+    val createdAt: Long = elapsedRealtime()
+) : AuthenticationStatus() {
     /**
      * Method that checks if [msgId] is a lockout error. A lockout error means that face
      * authentication is locked out.
diff --git a/packages/SystemUI/src/com/android/systemui/lifecycle/RepeatWhenAttached.kt b/packages/SystemUI/src/com/android/systemui/lifecycle/RepeatWhenAttached.kt
index 34a6740..e064839 100644
--- a/packages/SystemUI/src/com/android/systemui/lifecycle/RepeatWhenAttached.kt
+++ b/packages/SystemUI/src/com/android/systemui/lifecycle/RepeatWhenAttached.kt
@@ -167,9 +167,10 @@
         registry.currentState = Lifecycle.State.DESTROYED
     }
 
-    override fun getLifecycle(): Lifecycle {
-        return registry
-    }
+    override val lifecycle: Lifecycle
+        get() {
+            return registry
+        }
 
     private fun updateState() {
         registry.currentState =
diff --git a/packages/SystemUI/src/com/android/systemui/log/BouncerLogger.kt b/packages/SystemUI/src/com/android/systemui/log/BouncerLogger.kt
index 3226865..d4b799f 100644
--- a/packages/SystemUI/src/com/android/systemui/log/BouncerLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/log/BouncerLogger.kt
@@ -18,6 +18,7 @@
 
 import com.android.systemui.bouncer.shared.model.BouncerMessageModel
 import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.log.core.LogLevel
 import com.android.systemui.log.dagger.BouncerLog
 import javax.inject.Inject
 
diff --git a/packages/SystemUI/src/com/android/systemui/log/FaceAuthenticationLogger.kt b/packages/SystemUI/src/com/android/systemui/log/FaceAuthenticationLogger.kt
index fefa1b2..68cdfb6 100644
--- a/packages/SystemUI/src/com/android/systemui/log/FaceAuthenticationLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/log/FaceAuthenticationLogger.kt
@@ -6,7 +6,7 @@
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.keyguard.shared.model.ErrorAuthenticationStatus
 import com.android.systemui.keyguard.shared.model.TransitionStep
-import com.android.systemui.log.LogLevel.DEBUG
+import com.android.systemui.log.core.LogLevel.DEBUG
 import com.android.systemui.log.dagger.FaceAuthLog
 import javax.inject.Inject
 
diff --git a/packages/SystemUI/src/com/android/systemui/log/ScreenDecorationsLogger.kt b/packages/SystemUI/src/com/android/systemui/log/ScreenDecorationsLogger.kt
index 27301e9..150de26 100644
--- a/packages/SystemUI/src/com/android/systemui/log/ScreenDecorationsLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/log/ScreenDecorationsLogger.kt
@@ -21,9 +21,9 @@
 import android.graphics.RectF
 import androidx.core.graphics.toRectF
 import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.log.LogLevel.DEBUG
-import com.android.systemui.log.LogLevel.ERROR
-import com.android.systemui.log.LogLevel.INFO
+import com.android.systemui.log.core.LogLevel.DEBUG
+import com.android.systemui.log.core.LogLevel.ERROR
+import com.android.systemui.log.core.LogLevel.INFO
 import com.android.systemui.log.dagger.ScreenDecorationsLog
 import com.google.errorprone.annotations.CompileTimeConstant
 import javax.inject.Inject
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/DisplayMetricsRepoLog.kt b/packages/SystemUI/src/com/android/systemui/log/dagger/DisplayMetricsRepoLog.kt
new file mode 100644
index 0000000..fa9ec88
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/DisplayMetricsRepoLog.kt
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.log.dagger
+
+import javax.inject.Qualifier
+
+/** A [com.android.systemui.log.LogBuffer] for display metrics related logging. */
+@Qualifier
+@MustBeDocumented
+@Retention(AnnotationRetention.RUNTIME)
+annotation class DisplayMetricsRepoLog
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
index 3497285..b5759e3 100644
--- a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
@@ -488,4 +488,12 @@
     public static LogBuffer provideDreamLogBuffer(LogBufferFactory factory) {
         return factory.create("DreamLog", 250);
     }
+
+    /** Provides a {@link LogBuffer} for display metrics related logs. */
+    @Provides
+    @SysUISingleton
+    @DisplayMetricsRepoLog
+    public static LogBuffer provideDisplayMetricsRepoLogBuffer(LogBufferFactory factory) {
+        return factory.create("DisplayMetricsRepo", 50);
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/log/table/TableLogBuffer.kt b/packages/SystemUI/src/com/android/systemui/log/table/TableLogBuffer.kt
index 8d622ae..67a985e 100644
--- a/packages/SystemUI/src/com/android/systemui/log/table/TableLogBuffer.kt
+++ b/packages/SystemUI/src/com/android/systemui/log/table/TableLogBuffer.kt
@@ -21,8 +21,8 @@
 import com.android.systemui.Dumpable
 import com.android.systemui.common.buffer.RingBuffer
 import com.android.systemui.dagger.qualifiers.Background
-import com.android.systemui.log.LogLevel
 import com.android.systemui.log.LogcatEchoTracker
+import com.android.systemui.log.core.LogLevel
 import com.android.systemui.plugins.log.TableLogBufferBase
 import com.android.systemui.util.time.SystemClock
 import java.io.PrintWriter
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaTimeoutLogger.kt b/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaTimeoutLogger.kt
index e2e269d..534241e 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaTimeoutLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaTimeoutLogger.kt
@@ -19,7 +19,7 @@
 import android.media.session.PlaybackState
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.log.LogBuffer
-import com.android.systemui.log.LogLevel
+import com.android.systemui.log.core.LogLevel
 import com.android.systemui.log.dagger.MediaTimeoutListenerLog
 import javax.inject.Inject
 
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/resume/ResumeMediaBrowserLogger.kt b/packages/SystemUI/src/com/android/systemui/media/controls/resume/ResumeMediaBrowserLogger.kt
index 9e53d77..888b9c7 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/resume/ResumeMediaBrowserLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/resume/ResumeMediaBrowserLogger.kt
@@ -19,7 +19,7 @@
 import android.content.ComponentName
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.log.LogBuffer
-import com.android.systemui.log.LogLevel
+import com.android.systemui.log.core.LogLevel
 import com.android.systemui.log.dagger.MediaBrowserLog
 import javax.inject.Inject
 
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaCarouselControllerLogger.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaCarouselControllerLogger.kt
index 0ed2434..3dc0000 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaCarouselControllerLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaCarouselControllerLogger.kt
@@ -18,7 +18,7 @@
 
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.log.LogBuffer
-import com.android.systemui.log.LogLevel
+import com.android.systemui.log.core.LogLevel
 import com.android.systemui.log.dagger.MediaCarouselControllerLog
 import javax.inject.Inject
 
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaViewLogger.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaViewLogger.kt
index c781b76..8f1595d 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaViewLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaViewLogger.kt
@@ -18,7 +18,7 @@
 
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.log.LogBuffer
-import com.android.systemui.log.LogLevel
+import com.android.systemui.log.core.LogLevel
 import com.android.systemui.log.dagger.MediaViewLog
 import javax.inject.Inject
 
diff --git a/packages/SystemUI/src/com/android/systemui/media/muteawait/MediaMuteAwaitLogger.kt b/packages/SystemUI/src/com/android/systemui/media/muteawait/MediaMuteAwaitLogger.kt
index bbcf259..4171682 100644
--- a/packages/SystemUI/src/com/android/systemui/media/muteawait/MediaMuteAwaitLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/muteawait/MediaMuteAwaitLogger.kt
@@ -3,7 +3,7 @@
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.log.dagger.MediaMuteAwaitLog
 import com.android.systemui.log.LogBuffer
-import com.android.systemui.log.LogLevel
+import com.android.systemui.log.core.LogLevel
 import javax.inject.Inject
 
 /** Log messages for [MediaMuteAwaitConnectionManager]. */
diff --git a/packages/SystemUI/src/com/android/systemui/media/nearby/NearbyMediaDevicesLogger.kt b/packages/SystemUI/src/com/android/systemui/media/nearby/NearbyMediaDevicesLogger.kt
index 66399d5..46c0132 100644
--- a/packages/SystemUI/src/com/android/systemui/media/nearby/NearbyMediaDevicesLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/nearby/NearbyMediaDevicesLogger.kt
@@ -3,7 +3,7 @@
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.log.dagger.NearbyMediaDevicesLog
 import com.android.systemui.log.LogBuffer
-import com.android.systemui.log.LogLevel
+import com.android.systemui.log.core.LogLevel
 import javax.inject.Inject
 
 /** Log messages for [NearbyMediaDevicesManager]. */
diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttLoggerUtils.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttLoggerUtils.kt
index eeda1027..3c2226f 100644
--- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttLoggerUtils.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttLoggerUtils.kt
@@ -17,7 +17,7 @@
 package com.android.systemui.media.taptotransfer.common
 
 import com.android.systemui.log.LogBuffer
-import com.android.systemui.log.LogLevel
+import com.android.systemui.log.core.LogLevel
 
 /** A helper for logging media tap-to-transfer events. */
 object MediaTttLoggerUtils {
diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderLogger.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderLogger.kt
index 206e5e3..503afd3 100644
--- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderLogger.kt
@@ -20,7 +20,7 @@
 import com.android.internal.logging.InstanceId
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.log.LogBuffer
-import com.android.systemui.log.LogLevel
+import com.android.systemui.log.core.LogLevel
 import com.android.systemui.media.taptotransfer.common.MediaTttLoggerUtils
 import javax.inject.Inject
 
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavBarHelper.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavBarHelper.java
index 99c591f..8225c47 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavBarHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavBarHelper.java
@@ -462,7 +462,7 @@
      * @return Whether the IME is shown on top of the screen given the {@code vis} flag of
      * {@link InputMethodService} and the keyguard states.
      */
-    public boolean isImeShown(@InputMethodService.ImeWindowVisibility int vis) {
+    public boolean isImeShown(int vis) {
         View shadeWindowView =  mNotificationShadeWindowController.getWindowRootView();
         boolean isKeyguardShowing = mKeyguardStateController.isShowing();
         boolean imeVisibleOnShade = shadeWindowView != null && shadeWindowView.isAttachedToWindow()
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java
index 5bae1cb..682335e 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java
@@ -66,7 +66,6 @@
 import android.graphics.Rect;
 import android.graphics.RectF;
 import android.graphics.Region;
-import android.inputmethodservice.InputMethodService;
 import android.os.Binder;
 import android.os.Bundle;
 import android.os.Handler;
@@ -1048,9 +1047,8 @@
     // ----- CommandQueue Callbacks -----
 
     @Override
-    public void setImeWindowStatus(int displayId, IBinder token,
-            @InputMethodService.ImeWindowVisibility int vis,
-            @InputMethodService.BackDispositionMode int backDisposition, boolean showImeSwitcher) {
+    public void setImeWindowStatus(int displayId, IBinder token, int vis, int backDisposition,
+            boolean showImeSwitcher) {
         if (displayId != mDisplayId) {
             return;
         }
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java b/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java
index cecf043..3b32313e 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java
@@ -338,9 +338,8 @@
     }
 
     @Override
-    public void setImeWindowStatus(int displayId, IBinder token,
-            @InputMethodService.ImeWindowVisibility int vis,
-            @InputMethodService.BackDispositionMode int backDisposition, boolean showImeSwitcher) {
+    public void setImeWindowStatus(int displayId, IBinder token, int vis, int backDisposition,
+            boolean showImeSwitcher) {
         boolean imeShown = mNavBarHelper.isImeShown(vis);
         if (!imeShown) {
             // Count imperceptible changes as visible so we transition taskbar out quickly.
diff --git a/packages/SystemUI/src/com/android/systemui/privacy/logging/PrivacyLogger.kt b/packages/SystemUI/src/com/android/systemui/privacy/logging/PrivacyLogger.kt
index e56106d..f934346 100644
--- a/packages/SystemUI/src/com/android/systemui/privacy/logging/PrivacyLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/privacy/logging/PrivacyLogger.kt
@@ -20,8 +20,8 @@
 import android.permission.PermissionGroupUsage
 import com.android.systemui.log.dagger.PrivacyLog
 import com.android.systemui.log.LogBuffer
-import com.android.systemui.log.LogLevel
-import com.android.systemui.log.LogMessage
+import com.android.systemui.log.core.LogLevel
+import com.android.systemui.log.core.LogMessage
 import com.android.systemui.privacy.PrivacyDialog
 import com.android.systemui.privacy.PrivacyItem
 import java.util.Locale
diff --git a/packages/SystemUI/src/com/android/systemui/qs/AutoAddTracker.kt b/packages/SystemUI/src/com/android/systemui/qs/AutoAddTracker.kt
index c70cce9..2fafba1 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/AutoAddTracker.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/AutoAddTracker.kt
@@ -120,6 +120,7 @@
 
                     val tilesToRemove = restoredAutoAdded.filter { it !in restoredTiles }
                     if (tilesToRemove.isNotEmpty()) {
+                        Log.d(TAG, "Removing tiles: $tilesToRemove")
                         qsHost.removeTiles(tilesToRemove)
                     }
                     val tiles = synchronized(autoAdded) {
@@ -255,6 +256,7 @@
 
     override fun dump(pw: PrintWriter, args: Array<out String>) {
         pw.println("Current user: $userId")
+        pw.println("Restored tiles: $restoredTiles")
         pw.println("Added tiles: $autoAdded")
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSFragmentDisableFlagsLogger.kt b/packages/SystemUI/src/com/android/systemui/qs/QSFragmentDisableFlagsLogger.kt
index ac6aabb..6563e42 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSFragmentDisableFlagsLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSFragmentDisableFlagsLogger.kt
@@ -2,7 +2,7 @@
 
 import com.android.systemui.log.dagger.QSFragmentDisableLog
 import com.android.systemui.log.LogBuffer
-import com.android.systemui.log.LogLevel
+import com.android.systemui.log.core.LogLevel
 import com.android.systemui.statusbar.disableflags.DisableFlagsLogger
 import javax.inject.Inject
 
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java b/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java
index d2568ac..432147f 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java
@@ -152,6 +152,7 @@
         mQsFactories.add(defaultFactory);
         pluginManager.addPluginListener(this, QSFactory.class, true);
         mUserTracker = userTracker;
+        mCurrentUser = userTracker.getUserId();
         mSecureSettings = secureSettings;
         mCustomTileStatePersister = customTileStatePersister;
 
@@ -161,7 +162,9 @@
             // finishes before creating any tiles.
             tunerService.addTunable(this, TILES_SETTING);
             // AutoTileManager can modify mTiles so make sure mTiles has already been initialized.
-            mAutoTiles = autoTiles.get();
+            if (!mFeatureFlags.isEnabled(Flags.QS_PIPELINE_AUTO_ADD)) {
+                mAutoTiles = autoTiles.get();
+            }
         });
     }
 
@@ -272,6 +275,13 @@
         if (!TILES_SETTING.equals(key)) {
             return;
         }
+        int currentUser = mUserTracker.getUserId();
+        if (currentUser != mCurrentUser) {
+            mUserContext = mUserTracker.getUserContext();
+            if (mAutoTiles != null) {
+                mAutoTiles.changeUser(UserHandle.of(currentUser));
+            }
+        }
         // Do not process tiles if the flag is enabled.
         if (mFeatureFlags.isEnabled(Flags.QS_PIPELINE_NEW_HOST)) {
             return;
@@ -280,13 +290,6 @@
             newValue = mContext.getResources().getString(R.string.quick_settings_tiles_retail_mode);
         }
         final List<String> tileSpecs = loadTileSpecs(mContext, newValue);
-        int currentUser = mUserTracker.getUserId();
-        if (currentUser != mCurrentUser) {
-            mUserContext = mUserTracker.getUserContext();
-            if (mAutoTiles != null) {
-                mAutoTiles.changeUser(UserHandle.of(currentUser));
-            }
-        }
         if (tileSpecs.equals(mTileSpecs) && currentUser == mCurrentUser) return;
         Log.d(TAG, "Recreating tiles: " + tileSpecs);
         mTiles.entrySet().stream().filter(tile -> !tileSpecs.contains(tile.getKey())).forEach(
@@ -301,7 +304,7 @@
             if (tile != null && (!(tile instanceof CustomTile)
                     || ((CustomTile) tile).getUser() == currentUser)) {
                 if (tile.isAvailable()) {
-                    if (DEBUG) Log.d(TAG, "Adding " + tile);
+                    Log.d(TAG, "Adding " + tile);
                     tile.removeCallbacks();
                     if (!(tile instanceof CustomTile) && mCurrentUser != currentUser) {
                         tile.userSwitch(currentUser);
@@ -420,6 +423,7 @@
     // When calling this, you may want to modify mTilesListDirty accordingly.
     @MainThread
     private void saveTilesToSettings(List<String> tileSpecs) {
+        Log.d(TAG, "Saving tiles: " + tileSpecs + " for user: " + mCurrentUser);
         mSecureSettings.putStringForUser(TILES_SETTING, TextUtils.join(",", tileSpecs),
                 null /* tag */, false /* default */, mCurrentUser,
                 true /* overrideable by restore */);
@@ -493,7 +497,7 @@
                 lifecycleManager.flushMessagesAndUnbind();
             }
         }
-        if (DEBUG) Log.d(TAG, "saveCurrentTiles " + newTiles);
+        Log.d(TAG, "saveCurrentTiles " + newTiles);
         mTilesListDirty = true;
         saveTilesToSettings(newTiles);
     }
@@ -564,9 +568,9 @@
 
         if (TextUtils.isEmpty(tileList)) {
             tileList = res.getString(R.string.quick_settings_tiles);
-            if (DEBUG) Log.d(TAG, "Loaded tile specs from default config: " + tileList);
+            Log.d(TAG, "Loaded tile specs from default config: " + tileList);
         } else {
-            if (DEBUG) Log.d(TAG, "Loaded tile specs from setting: " + tileList);
+            Log.d(TAG, "Loaded tile specs from setting: " + tileList);
         }
         final ArrayList<String> tiles = new ArrayList<String>();
         boolean addedDefault = false;
@@ -612,6 +616,10 @@
     @Override
     public void dump(PrintWriter pw, String[] args) {
         pw.println("QSTileHost:");
+        pw.println("tile specs: " + mTileSpecs);
+        pw.println("current user: " + mCurrentUser);
+        pw.println("is dirty: " + mTilesListDirty);
+        pw.println("tiles:");
         mTiles.values().stream().filter(obj -> obj instanceof Dumpable)
                 .forEach(o -> ((Dumpable) o).dump(pw, args));
     }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/dagger/QSModule.java b/packages/SystemUI/src/com/android/systemui/qs/dagger/QSModule.java
index dffe7fd..03de3a0 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/dagger/QSModule.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/dagger/QSModule.java
@@ -19,9 +19,9 @@
 import static com.android.systemui.qs.dagger.QSFlagsModule.RBC_AVAILABLE;
 
 import android.content.Context;
-import android.hardware.display.NightDisplayListener;
 import android.os.Handler;
 
+import com.android.systemui.dagger.NightDisplayListenerModule;
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dagger.qualifiers.Background;
 import com.android.systemui.media.dagger.MediaModule;
@@ -41,14 +41,14 @@
 import com.android.systemui.statusbar.policy.WalletController;
 import com.android.systemui.util.settings.SecureSettings;
 
-import dagger.Module;
-import dagger.Provides;
-import dagger.multibindings.Multibinds;
-
 import java.util.Map;
 
 import javax.inject.Named;
 
+import dagger.Module;
+import dagger.Provides;
+import dagger.multibindings.Multibinds;
+
 /**
  * Module for QS dependencies
  */
@@ -79,7 +79,7 @@
             HotspotController hotspotController,
             DataSaverController dataSaverController,
             ManagedProfileController managedProfileController,
-            NightDisplayListener nightDisplayListener,
+            NightDisplayListenerModule.Builder nightDisplayListenerBuilder,
             CastController castController,
             ReduceBrightColorsController reduceBrightColorsController,
             DeviceControlsController deviceControlsController,
@@ -95,7 +95,7 @@
                 hotspotController,
                 dataSaverController,
                 managedProfileController,
-                nightDisplayListener,
+                nightDisplayListenerBuilder,
                 castController,
                 reduceBrightColorsController,
                 deviceControlsController,
diff --git a/packages/SystemUI/src/com/android/systemui/qs/external/TileRequestDialog.kt b/packages/SystemUI/src/com/android/systemui/qs/external/TileRequestDialog.kt
index 6265b3c..3432628 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/external/TileRequestDialog.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/external/TileRequestDialog.kt
@@ -18,6 +18,7 @@
 
 import android.content.Context
 import android.graphics.drawable.Icon
+import android.view.ContextThemeWrapper
 import android.view.LayoutInflater
 import android.view.ViewGroup
 import android.widget.TextView
@@ -66,7 +67,8 @@
     }
 
     private fun createTileView(tileData: TileData): QSTileView {
-        val tile = QSTileViewImpl(context, QSIconViewImpl(context), true)
+        val themedContext = ContextThemeWrapper(context, R.style.Theme_SystemUI_QuickSettings)
+        val tile = QSTileViewImpl(themedContext, QSIconViewImpl(themedContext), true)
         val state = QSTile.BooleanState().apply {
             label = tileData.label
             handlesLongClick = false
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 9c9ad33..3c53d77 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
@@ -244,8 +244,8 @@
 
         val backgroundResource =
             when (model.backgroundColor) {
-                R.attr.offStateColor -> R.drawable.qs_footer_action_circle
-                com.android.internal.R.attr.colorAccent -> R.drawable.qs_footer_action_circle_color
+                R.attr.shadeInactive -> R.drawable.qs_footer_action_circle
+                R.attr.shadeActive -> R.drawable.qs_footer_action_circle_color
                 else -> error("Unsupported icon background resource ${model.backgroundColor}")
             }
         buttonView.setBackgroundResource(backgroundResource)
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 b3596a2..32146b5 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
@@ -145,8 +145,12 @@
                 R.drawable.ic_settings,
                 ContentDescription.Resource(R.string.accessibility_quick_settings_settings)
             ),
-            iconTint = null,
-            backgroundColor = R.attr.offStateColor,
+            iconTint =
+                Utils.getColorAttrDefaultColor(
+                    context,
+                    R.attr.onShadeInactiveVariant,
+                ),
+            backgroundColor = R.attr.shadeInactive,
             this::onSettingsButtonClicked,
         )
 
@@ -162,9 +166,9 @@
                 iconTint =
                     Utils.getColorAttrDefaultColor(
                         context,
-                        com.android.internal.R.attr.textColorOnAccent,
+                        R.attr.onShadeActive,
                     ),
-                backgroundColor = com.android.internal.R.attr.colorAccent,
+                backgroundColor = R.attr.shadeActive,
                 this::onPowerButtonClicked,
             )
         } else {
@@ -264,7 +268,7 @@
                     ),
                 ),
             iconTint = null,
-            backgroundColor = R.attr.offStateColor,
+            backgroundColor = R.attr.shadeInactive,
             onClick = this::onUserSwitcherClicked,
         )
     }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/logging/QSLogger.kt b/packages/SystemUI/src/com/android/systemui/qs/logging/QSLogger.kt
index c00a81c..39745c8 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/logging/QSLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/logging/QSLogger.kt
@@ -24,9 +24,9 @@
 import com.android.systemui.log.ConstantStringsLogger
 import com.android.systemui.log.ConstantStringsLoggerImpl
 import com.android.systemui.log.LogBuffer
-import com.android.systemui.log.LogLevel.DEBUG
-import com.android.systemui.log.LogLevel.ERROR
-import com.android.systemui.log.LogLevel.VERBOSE
+import com.android.systemui.log.core.LogLevel.DEBUG
+import com.android.systemui.log.core.LogLevel.ERROR
+import com.android.systemui.log.core.LogLevel.VERBOSE
 import com.android.systemui.log.dagger.QSConfigLog
 import com.android.systemui.log.dagger.QSLog
 import com.android.systemui.plugins.qs.QSTile
diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/dagger/BaseAutoAddableModule.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/dagger/BaseAutoAddableModule.kt
new file mode 100644
index 0000000..adea26e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/dagger/BaseAutoAddableModule.kt
@@ -0,0 +1,73 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.pipeline.dagger
+
+import android.content.res.Resources
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.qs.pipeline.domain.autoaddable.AutoAddableSetting
+import com.android.systemui.qs.pipeline.domain.autoaddable.AutoAddableSettingList
+import com.android.systemui.qs.pipeline.domain.autoaddable.CastAutoAddable
+import com.android.systemui.qs.pipeline.domain.autoaddable.DataSaverAutoAddable
+import com.android.systemui.qs.pipeline.domain.autoaddable.DeviceControlsAutoAddable
+import com.android.systemui.qs.pipeline.domain.autoaddable.HotspotAutoAddable
+import com.android.systemui.qs.pipeline.domain.autoaddable.NightDisplayAutoAddable
+import com.android.systemui.qs.pipeline.domain.autoaddable.ReduceBrightColorsAutoAddable
+import com.android.systemui.qs.pipeline.domain.autoaddable.WalletAutoAddable
+import com.android.systemui.qs.pipeline.domain.autoaddable.WorkTileAutoAddable
+import com.android.systemui.qs.pipeline.domain.model.AutoAddable
+import dagger.Binds
+import dagger.Module
+import dagger.Provides
+import dagger.multibindings.ElementsIntoSet
+import dagger.multibindings.IntoSet
+
+@Module
+interface BaseAutoAddableModule {
+
+    companion object {
+        @Provides
+        @ElementsIntoSet
+        fun providesAutoAddableSetting(
+            @Main resources: Resources,
+            autoAddableSettingFactory: AutoAddableSetting.Factory
+        ): Set<AutoAddable> {
+            return AutoAddableSettingList.parseSettingsResource(
+                    resources,
+                    autoAddableSettingFactory
+                )
+                .toSet()
+        }
+    }
+
+    @Binds @IntoSet fun bindCastAutoAddable(impl: CastAutoAddable): AutoAddable
+
+    @Binds @IntoSet fun bindDataSaverAutoAddable(impl: DataSaverAutoAddable): AutoAddable
+
+    @Binds @IntoSet fun bindDeviceControlsAutoAddable(impl: DeviceControlsAutoAddable): AutoAddable
+
+    @Binds @IntoSet fun bindHotspotAutoAddable(impl: HotspotAutoAddable): AutoAddable
+
+    @Binds @IntoSet fun bindNightDisplayAutoAddable(impl: NightDisplayAutoAddable): AutoAddable
+
+    @Binds
+    @IntoSet
+    fun bindReduceBrightColorsAutoAddable(impl: ReduceBrightColorsAutoAddable): AutoAddable
+
+    @Binds @IntoSet fun bindWalletAutoAddable(impl: WalletAutoAddable): AutoAddable
+
+    @Binds @IntoSet fun bindWorkModeAutoAddable(impl: WorkTileAutoAddable): AutoAddable
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/dagger/QSAutoAddLog.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/dagger/QSAutoAddLog.kt
new file mode 100644
index 0000000..91cb5bb
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/dagger/QSAutoAddLog.kt
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.pipeline.dagger
+
+import javax.inject.Qualifier
+
+/** A [LogBuffer] for the QS pipeline to track auto-added tiles */
+@Qualifier @MustBeDocumented @Retention(AnnotationRetention.RUNTIME) annotation class QSAutoAddLog
diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/dagger/QSAutoAddModule.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/dagger/QSAutoAddModule.kt
index 9979228..a010ac4 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/pipeline/dagger/QSAutoAddModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/dagger/QSAutoAddModule.kt
@@ -16,13 +16,40 @@
 
 package com.android.systemui.qs.pipeline.dagger
 
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.LogBufferFactory
 import com.android.systemui.qs.pipeline.data.repository.AutoAddRepository
 import com.android.systemui.qs.pipeline.data.repository.AutoAddSettingRepository
+import com.android.systemui.qs.pipeline.domain.model.AutoAddable
+import com.android.systemui.qs.pipeline.shared.logging.QSPipelineLogger
 import dagger.Binds
 import dagger.Module
+import dagger.Provides
+import dagger.multibindings.Multibinds
 
-@Module
+@Module(
+    includes =
+        [
+            BaseAutoAddableModule::class,
+        ]
+)
 abstract class QSAutoAddModule {
 
     @Binds abstract fun bindAutoAddRepository(impl: AutoAddSettingRepository): AutoAddRepository
+
+    @Multibinds abstract fun providesAutoAddableSet(): Set<AutoAddable>
+
+    companion object {
+        /**
+         * Provides a logging buffer for all logs related to the new Quick Settings pipeline to log
+         * auto added tiles.
+         */
+        @Provides
+        @SysUISingleton
+        @QSAutoAddLog
+        fun provideQSAutoAddLogBuffer(factory: LogBufferFactory): LogBuffer {
+            return factory.create(QSPipelineLogger.AUTO_ADD_TAG, maxSize = 100, systrace = false)
+        }
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/dagger/QSPipelineModule.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/dagger/QSPipelineModule.kt
index d7ae575..a4600fb 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/pipeline/dagger/QSPipelineModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/dagger/QSPipelineModule.kt
@@ -26,7 +26,7 @@
 import com.android.systemui.qs.pipeline.data.repository.TileSpecSettingsRepository
 import com.android.systemui.qs.pipeline.domain.interactor.CurrentTilesInteractor
 import com.android.systemui.qs.pipeline.domain.interactor.CurrentTilesInteractorImpl
-import com.android.systemui.qs.pipeline.prototyping.PrototypeCoreStartable
+import com.android.systemui.qs.pipeline.domain.startable.QSPipelineCoreStartable
 import com.android.systemui.qs.pipeline.shared.logging.QSPipelineLogger
 import dagger.Binds
 import dagger.Module
@@ -53,8 +53,8 @@
 
     @Binds
     @IntoMap
-    @ClassKey(PrototypeCoreStartable::class)
-    abstract fun providePrototypeCoreStartable(startable: PrototypeCoreStartable): CoreStartable
+    @ClassKey(QSPipelineCoreStartable::class)
+    abstract fun provideCoreStartable(startable: QSPipelineCoreStartable): CoreStartable
 
     companion object {
         /**
diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/dagger/QSTileListLog.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/dagger/QSTileListLog.kt
index ad8bfea..c56ca8c 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/pipeline/dagger/QSTileListLog.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/dagger/QSTileListLog.kt
@@ -19,5 +19,5 @@
 import java.lang.annotation.RetentionPolicy
 import javax.inject.Qualifier
 
-/** A {@link LogBuffer} for the new QS Pipeline for logging changes to the set of current tiles. */
+/** A [LogBuffer] for the new QS Pipeline for logging changes to the set of current tiles. */
 @Qualifier @MustBeDocumented @Retention(RetentionPolicy.RUNTIME) annotation class QSTileListLog
diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/autoaddable/AutoAddableSetting.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/autoaddable/AutoAddableSetting.kt
new file mode 100644
index 0000000..45129b9
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/autoaddable/AutoAddableSetting.kt
@@ -0,0 +1,82 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.pipeline.domain.autoaddable
+
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.qs.pipeline.domain.model.AutoAddSignal
+import com.android.systemui.qs.pipeline.domain.model.AutoAddTracking
+import com.android.systemui.qs.pipeline.domain.model.AutoAddable
+import com.android.systemui.qs.pipeline.shared.TileSpec
+import com.android.systemui.util.settings.SecureSettings
+import com.android.systemui.util.settings.SettingsProxyExt.observerFlow
+import dagger.assisted.Assisted
+import dagger.assisted.AssistedFactory
+import dagger.assisted.AssistedInject
+import java.util.Objects
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.filter
+import kotlinx.coroutines.flow.flowOn
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.onStart
+
+/**
+ * It tracks a specific `Secure` int [setting] and when its value changes to non-zero, it will emit
+ * a [AutoAddSignal.Add] for [spec].
+ */
+class AutoAddableSetting
+@AssistedInject
+constructor(
+    private val secureSettings: SecureSettings,
+    @Background private val bgDispatcher: CoroutineDispatcher,
+    @Assisted private val setting: String,
+    @Assisted private val spec: TileSpec,
+) : AutoAddable {
+
+    override fun autoAddSignal(userId: Int): Flow<AutoAddSignal> {
+        return secureSettings
+            .observerFlow(userId, setting)
+            .onStart { emit(Unit) }
+            .map { secureSettings.getIntForUser(setting, 0, userId) != 0 }
+            .distinctUntilChanged()
+            .filter { it }
+            .map { AutoAddSignal.Add(spec) }
+            .flowOn(bgDispatcher)
+    }
+
+    override val autoAddTracking = AutoAddTracking.IfNotAdded(spec)
+
+    override val description = "AutoAddableSetting: $setting:$spec ($autoAddTracking)"
+
+    override fun equals(other: Any?): Boolean {
+        return other is AutoAddableSetting && spec == other.spec && setting == other.setting
+    }
+
+    override fun hashCode(): Int {
+        return Objects.hash(spec, setting)
+    }
+
+    override fun toString(): String {
+        return description
+    }
+
+    @AssistedFactory
+    interface Factory {
+        fun create(setting: String, spec: TileSpec): AutoAddableSetting
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/autoaddable/AutoAddableSettingList.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/autoaddable/AutoAddableSettingList.kt
new file mode 100644
index 0000000..b1c7433
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/autoaddable/AutoAddableSettingList.kt
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.pipeline.domain.autoaddable
+
+import android.content.res.Resources
+import android.util.Log
+import com.android.systemui.R
+import com.android.systemui.qs.pipeline.domain.model.AutoAddable
+import com.android.systemui.qs.pipeline.shared.TileSpec
+
+object AutoAddableSettingList {
+
+    /** Parses [R.array.config_quickSettingsAutoAdd] into a collection of [AutoAddableSetting]. */
+    fun parseSettingsResource(
+        resources: Resources,
+        autoAddableSettingFactory: AutoAddableSetting.Factory,
+    ): Iterable<AutoAddable> {
+        val autoAddList = resources.getStringArray(R.array.config_quickSettingsAutoAdd)
+        return autoAddList.mapNotNull {
+            val elements = it.split(SETTING_SEPARATOR, limit = 2)
+            if (elements.size == 2) {
+                val setting = elements[0]
+                val spec = elements[1]
+                val tileSpec = TileSpec.create(spec)
+                if (tileSpec == TileSpec.Invalid) {
+                    Log.w(TAG, "Malformed item in array: $it")
+                    null
+                } else {
+                    autoAddableSettingFactory.create(setting, TileSpec.create(spec))
+                }
+            } else {
+                Log.w(TAG, "Malformed item in array: $it")
+                null
+            }
+        }
+    }
+
+    private const val SETTING_SEPARATOR = ":"
+    private const val TAG = "AutoAddableSettingList"
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/autoaddable/CallbackControllerAutoAddable.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/autoaddable/CallbackControllerAutoAddable.kt
new file mode 100644
index 0000000..88a49ee
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/autoaddable/CallbackControllerAutoAddable.kt
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.pipeline.domain.autoaddable
+
+import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
+import com.android.systemui.qs.pipeline.domain.model.AutoAddSignal
+import com.android.systemui.qs.pipeline.domain.model.AutoAddTracking
+import com.android.systemui.qs.pipeline.domain.model.AutoAddable
+import com.android.systemui.qs.pipeline.shared.TileSpec
+import com.android.systemui.statusbar.policy.CallbackController
+import kotlinx.coroutines.channels.ProducerScope
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.Flow
+
+/** Generic [AutoAddable] for tiles that are added based on a signal from a [CallbackController]. */
+abstract class CallbackControllerAutoAddable<
+    Callback : Any, Controller : CallbackController<Callback>>(
+    private val controller: Controller,
+) : AutoAddable {
+
+    /** [TileSpec] for the tile to add. */
+    protected abstract val spec: TileSpec
+
+    /**
+     * Callback to be used to determine when to add the tile. When the callback determines that the
+     * feature has been enabled, it should call [sendAdd].
+     */
+    protected abstract fun ProducerScope<AutoAddSignal>.getCallback(): Callback
+
+    /** Sends an [AutoAddSignal.Add] for [spec]. */
+    protected fun ProducerScope<AutoAddSignal>.sendAdd() {
+        trySend(AutoAddSignal.Add(spec))
+    }
+
+    final override fun autoAddSignal(userId: Int): Flow<AutoAddSignal> {
+        return conflatedCallbackFlow {
+            val callback = getCallback()
+            controller.addCallback(callback)
+
+            awaitClose { controller.removeCallback(callback) }
+        }
+    }
+
+    override val autoAddTracking: AutoAddTracking
+        get() = AutoAddTracking.IfNotAdded(spec)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/autoaddable/CastAutoAddable.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/autoaddable/CastAutoAddable.kt
new file mode 100644
index 0000000..b5bef9f
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/autoaddable/CastAutoAddable.kt
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.pipeline.domain.autoaddable
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.qs.pipeline.domain.model.AutoAddSignal
+import com.android.systemui.qs.pipeline.shared.TileSpec
+import com.android.systemui.qs.tiles.CastTile
+import com.android.systemui.statusbar.policy.CastController
+import javax.inject.Inject
+import kotlinx.coroutines.channels.ProducerScope
+
+/**
+ * [AutoAddable] for [CastTile.TILE_SPEC].
+ *
+ * It will send a signal to add the tile when there's a casting device connected or connecting.
+ */
+@SysUISingleton
+class CastAutoAddable
+@Inject
+constructor(
+    private val controller: CastController,
+) : CallbackControllerAutoAddable<CastController.Callback, CastController>(controller) {
+
+    override val spec: TileSpec
+        get() = TileSpec.create(CastTile.TILE_SPEC)
+
+    override fun ProducerScope<AutoAddSignal>.getCallback(): CastController.Callback {
+        return CastController.Callback {
+            val isCasting =
+                controller.castDevices.any {
+                    it.state == CastController.CastDevice.STATE_CONNECTED ||
+                        it.state == CastController.CastDevice.STATE_CONNECTING
+                }
+            if (isCasting) {
+                sendAdd()
+            }
+        }
+    }
+
+    override val description = "CastAutoAddable ($autoAddTracking)"
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/autoaddable/DataSaverAutoAddable.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/autoaddable/DataSaverAutoAddable.kt
new file mode 100644
index 0000000..a877aee
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/autoaddable/DataSaverAutoAddable.kt
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.pipeline.domain.autoaddable
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.qs.pipeline.domain.model.AutoAddSignal
+import com.android.systemui.qs.pipeline.shared.TileSpec
+import com.android.systemui.qs.tiles.DataSaverTile
+import com.android.systemui.statusbar.policy.DataSaverController
+import javax.inject.Inject
+import kotlinx.coroutines.channels.ProducerScope
+
+/**
+ * [AutoAddable] for [DataSaverTile.TILE_SPEC].
+ *
+ * It will send a signal to add the tile when data saver is enabled.
+ */
+@SysUISingleton
+class DataSaverAutoAddable
+@Inject
+constructor(
+    dataSaverController: DataSaverController,
+) :
+    CallbackControllerAutoAddable<DataSaverController.Listener, DataSaverController>(
+        dataSaverController
+    ) {
+
+    override val spec
+        get() = TileSpec.create(DataSaverTile.TILE_SPEC)
+
+    override fun ProducerScope<AutoAddSignal>.getCallback(): DataSaverController.Listener {
+        return DataSaverController.Listener { enabled ->
+            if (enabled) {
+                sendAdd()
+            }
+        }
+    }
+
+    override val description = "DataSaverAutoAddable ($autoAddTracking)"
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/autoaddable/DeviceControlsAutoAddable.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/autoaddable/DeviceControlsAutoAddable.kt
new file mode 100644
index 0000000..76bfad9
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/autoaddable/DeviceControlsAutoAddable.kt
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.pipeline.domain.autoaddable
+
+import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.qs.pipeline.domain.model.AutoAddSignal
+import com.android.systemui.qs.pipeline.domain.model.AutoAddTracking
+import com.android.systemui.qs.pipeline.domain.model.AutoAddable
+import com.android.systemui.qs.pipeline.shared.TileSpec
+import com.android.systemui.qs.tiles.DeviceControlsTile
+import com.android.systemui.statusbar.policy.DeviceControlsController
+import javax.inject.Inject
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.Flow
+
+/**
+ * [AutoAddable] for [DeviceControlsTile.TILE_SPEC].
+ *
+ * It will send a signal to add the tile when updating to a device that supports device controls. It
+ * will send a signal to remove the tile when the device does not support controls.
+ */
+@SysUISingleton
+class DeviceControlsAutoAddable
+@Inject
+constructor(
+    private val deviceControlsController: DeviceControlsController,
+) : AutoAddable {
+
+    private val spec = TileSpec.create(DeviceControlsTile.TILE_SPEC)
+
+    override fun autoAddSignal(userId: Int): Flow<AutoAddSignal> {
+        return conflatedCallbackFlow {
+            val callback =
+                object : DeviceControlsController.Callback {
+                    override fun onControlsUpdate(position: Int?) {
+                        position?.let { trySend(AutoAddSignal.Add(spec, position)) }
+                        deviceControlsController.removeCallback()
+                    }
+
+                    override fun removeControlsAutoTracker() {
+                        trySend(AutoAddSignal.Remove(spec))
+                    }
+                }
+
+            deviceControlsController.setCallback(callback)
+
+            awaitClose { deviceControlsController.removeCallback() }
+        }
+    }
+
+    override val autoAddTracking: AutoAddTracking
+        get() = AutoAddTracking.Always
+
+    override val description = "DeviceControlsAutoAddable ($autoAddTracking)"
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/autoaddable/HotspotAutoAddable.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/autoaddable/HotspotAutoAddable.kt
new file mode 100644
index 0000000..9c59e12
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/autoaddable/HotspotAutoAddable.kt
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.pipeline.domain.autoaddable
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.qs.pipeline.domain.model.AutoAddSignal
+import com.android.systemui.qs.pipeline.shared.TileSpec
+import com.android.systemui.qs.tiles.HotspotTile
+import com.android.systemui.statusbar.policy.HotspotController
+import javax.inject.Inject
+import kotlinx.coroutines.channels.ProducerScope
+
+/**
+ * [AutoAddable] for [HotspotTile.TILE_SPEC].
+ *
+ * It will send a signal to add the tile when hotspot is enabled.
+ */
+@SysUISingleton
+class HotspotAutoAddable
+@Inject
+constructor(
+    hotspotController: HotspotController,
+) :
+    CallbackControllerAutoAddable<HotspotController.Callback, HotspotController>(
+        hotspotController
+    ) {
+
+    override val spec
+        get() = TileSpec.create(HotspotTile.TILE_SPEC)
+
+    override fun ProducerScope<AutoAddSignal>.getCallback(): HotspotController.Callback {
+        return HotspotController.Callback { enabled, _ ->
+            if (enabled) {
+                sendAdd()
+            }
+        }
+    }
+
+    override val description = "HotspotAutoAddable ($autoAddTracking)"
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/autoaddable/NightDisplayAutoAddable.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/autoaddable/NightDisplayAutoAddable.kt
new file mode 100644
index 0000000..31ea734
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/autoaddable/NightDisplayAutoAddable.kt
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.pipeline.domain.autoaddable
+
+import android.content.Context
+import android.hardware.display.ColorDisplayManager
+import android.hardware.display.NightDisplayListener
+import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
+import com.android.systemui.dagger.NightDisplayListenerModule
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.qs.pipeline.domain.model.AutoAddSignal
+import com.android.systemui.qs.pipeline.domain.model.AutoAddTracking
+import com.android.systemui.qs.pipeline.domain.model.AutoAddable
+import com.android.systemui.qs.pipeline.shared.TileSpec
+import com.android.systemui.qs.tiles.NightDisplayTile
+import javax.inject.Inject
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.Flow
+
+/**
+ * [AutoAddable] for [NightDisplayTile.TILE_SPEC].
+ *
+ * It will send a signal to add the tile when night display is enabled or when the auto mode changes
+ * to one that supports night display.
+ */
+@SysUISingleton
+class NightDisplayAutoAddable
+@Inject
+constructor(
+    private val nightDisplayListenerBuilder: NightDisplayListenerModule.Builder,
+    context: Context,
+) : AutoAddable {
+
+    private val enabled = ColorDisplayManager.isNightDisplayAvailable(context)
+    private val spec = TileSpec.create(NightDisplayTile.TILE_SPEC)
+
+    override fun autoAddSignal(userId: Int): Flow<AutoAddSignal> {
+        return conflatedCallbackFlow {
+            val nightDisplayListener = nightDisplayListenerBuilder.setUser(userId).build()
+
+            val callback =
+                object : NightDisplayListener.Callback {
+                    override fun onActivated(activated: Boolean) {
+                        if (activated) {
+                            sendAdd()
+                        }
+                    }
+
+                    override fun onAutoModeChanged(autoMode: Int) {
+                        if (
+                            autoMode == ColorDisplayManager.AUTO_MODE_CUSTOM_TIME ||
+                                autoMode == ColorDisplayManager.AUTO_MODE_TWILIGHT
+                        ) {
+                            sendAdd()
+                        }
+                    }
+
+                    private fun sendAdd() {
+                        trySend(AutoAddSignal.Add(spec))
+                    }
+                }
+
+            nightDisplayListener.setCallback(callback)
+
+            awaitClose { nightDisplayListener.setCallback(null) }
+        }
+    }
+
+    override val autoAddTracking =
+        if (enabled) {
+            AutoAddTracking.IfNotAdded(spec)
+        } else {
+            AutoAddTracking.Disabled
+        }
+
+    override val description = "NightDisplayAutoAddable ($autoAddTracking)"
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/autoaddable/ReduceBrightColorsAutoAddable.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/autoaddable/ReduceBrightColorsAutoAddable.kt
new file mode 100644
index 0000000..267e2b7
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/autoaddable/ReduceBrightColorsAutoAddable.kt
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.pipeline.domain.autoaddable
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.qs.ReduceBrightColorsController
+import com.android.systemui.qs.dagger.QSFlagsModule.RBC_AVAILABLE
+import com.android.systemui.qs.pipeline.domain.model.AutoAddSignal
+import com.android.systemui.qs.pipeline.domain.model.AutoAddTracking
+import com.android.systemui.qs.pipeline.shared.TileSpec
+import com.android.systemui.qs.tiles.ReduceBrightColorsTile
+import javax.inject.Inject
+import javax.inject.Named
+import kotlinx.coroutines.channels.ProducerScope
+
+/**
+ * [AutoAddable] for [ReduceBrightColorsTile.TILE_SPEC].
+ *
+ * It will send a signal to add the tile when reduce bright colors is enabled.
+ */
+@SysUISingleton
+class ReduceBrightColorsAutoAddable
+@Inject
+constructor(
+    controller: ReduceBrightColorsController,
+    @Named(RBC_AVAILABLE) private val available: Boolean,
+) :
+    CallbackControllerAutoAddable<
+        ReduceBrightColorsController.Listener, ReduceBrightColorsController
+    >(controller) {
+
+    override val spec: TileSpec
+        get() = TileSpec.create(ReduceBrightColorsTile.TILE_SPEC)
+
+    override fun ProducerScope<AutoAddSignal>.getCallback(): ReduceBrightColorsController.Listener {
+        return object : ReduceBrightColorsController.Listener {
+            override fun onActivated(activated: Boolean) {
+                if (activated) {
+                    sendAdd()
+                }
+            }
+        }
+    }
+
+    override val autoAddTracking
+        get() =
+            if (available) {
+                super.autoAddTracking
+            } else {
+                AutoAddTracking.Disabled
+            }
+
+    override val description = "ReduceBrightColorsAutoAddable ($autoAddTracking)"
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/autoaddable/SafetyCenterAutoAddable.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/autoaddable/SafetyCenterAutoAddable.kt
new file mode 100644
index 0000000..58a31bc
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/autoaddable/SafetyCenterAutoAddable.kt
@@ -0,0 +1,94 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.pipeline.domain.autoaddable
+
+import android.content.ComponentName
+import android.content.pm.PackageManager
+import android.content.res.Resources
+import android.text.TextUtils
+import com.android.systemui.R
+import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.qs.pipeline.domain.model.AutoAddSignal
+import com.android.systemui.qs.pipeline.domain.model.AutoAddTracking
+import com.android.systemui.qs.pipeline.domain.model.AutoAddable
+import com.android.systemui.qs.pipeline.shared.TileSpec
+import com.android.systemui.statusbar.policy.SafetyController
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.withContext
+
+/**
+ * [AutoAddable] for the safety tile.
+ *
+ * It will send a signal to add the tile when the feature is enabled, indicating the component
+ * corresponding to the tile. If the feature is disabled, it will send a signal to remove the tile.
+ */
+@SysUISingleton
+class SafetyCenterAutoAddable
+@Inject
+constructor(
+    private val safetyController: SafetyController,
+    private val packageManager: PackageManager,
+    @Main private val resources: Resources,
+    @Background private val bgDispatcher: CoroutineDispatcher,
+) : AutoAddable {
+
+    private suspend fun getSpec(): TileSpec? {
+        val specClass = resources.getString(R.string.safety_quick_settings_tile_class)
+        return if (TextUtils.isEmpty(specClass)) {
+            null
+        } else {
+            val packageName =
+                withContext(bgDispatcher) { packageManager.permissionControllerPackageName }
+            TileSpec.create(ComponentName(packageName, specClass))
+        }
+    }
+
+    override fun autoAddSignal(userId: Int): Flow<AutoAddSignal> {
+        return conflatedCallbackFlow {
+            val spec = getSpec()
+            if (spec != null) {
+                // If not added, we always try to add it
+                trySend(AutoAddSignal.Add(spec))
+                val listener =
+                    SafetyController.Listener { isSafetyCenterEnabled ->
+                        if (isSafetyCenterEnabled) {
+                            trySend(AutoAddSignal.Add(spec))
+                        } else {
+                            trySend(AutoAddSignal.Remove(spec))
+                        }
+                    }
+
+                safetyController.addCallback(listener)
+
+                awaitClose { safetyController.removeCallback(listener) }
+            } else {
+                awaitClose {}
+            }
+        }
+    }
+
+    override val autoAddTracking: AutoAddTracking
+        get() = AutoAddTracking.Always
+
+    override val description = "SafetyCenterAutoAddable ($autoAddTracking)"
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/autoaddable/WalletAutoAddable.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/autoaddable/WalletAutoAddable.kt
new file mode 100644
index 0000000..b3bc25f
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/autoaddable/WalletAutoAddable.kt
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.pipeline.domain.autoaddable
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.qs.pipeline.domain.model.AutoAddSignal
+import com.android.systemui.qs.pipeline.domain.model.AutoAddTracking
+import com.android.systemui.qs.pipeline.domain.model.AutoAddable
+import com.android.systemui.qs.pipeline.shared.TileSpec
+import com.android.systemui.qs.tiles.QuickAccessWalletTile
+import com.android.systemui.statusbar.policy.WalletController
+import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.flow
+
+/**
+ * [AutoAddable] for [QuickAccessWalletTile.TILE_SPEC].
+ *
+ * It will always try to add the tile if [WalletController.getWalletPosition] is non-null.
+ */
+@SysUISingleton
+class WalletAutoAddable
+@Inject
+constructor(
+    private val walletController: WalletController,
+) : AutoAddable {
+
+    private val spec = TileSpec.create(QuickAccessWalletTile.TILE_SPEC)
+
+    override fun autoAddSignal(userId: Int): Flow<AutoAddSignal> {
+        return flow {
+            val position = walletController.getWalletPosition()
+            if (position != null) {
+                emit(AutoAddSignal.Add(spec, position))
+            }
+        }
+    }
+
+    override val autoAddTracking: AutoAddTracking
+        get() = AutoAddTracking.IfNotAdded(spec)
+
+    override val description = "WalletAutoAddable ($autoAddTracking)"
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/autoaddable/WorkTileAutoAddable.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/autoaddable/WorkTileAutoAddable.kt
new file mode 100644
index 0000000..5e3c348
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/autoaddable/WorkTileAutoAddable.kt
@@ -0,0 +1,73 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.pipeline.domain.autoaddable
+
+import android.content.pm.UserInfo
+import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.qs.pipeline.domain.model.AutoAddSignal
+import com.android.systemui.qs.pipeline.domain.model.AutoAddTracking
+import com.android.systemui.qs.pipeline.domain.model.AutoAddable
+import com.android.systemui.qs.pipeline.shared.TileSpec
+import com.android.systemui.qs.tiles.WorkModeTile
+import com.android.systemui.settings.UserTracker
+import javax.inject.Inject
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.Flow
+
+/**
+ * [AutoAddable] for [WorkModeTile.TILE_SPEC].
+ *
+ * It will send a signal to add the tile when there is a managed profile for the current user, and a
+ * signal to remove it if there is not.
+ */
+@SysUISingleton
+class WorkTileAutoAddable @Inject constructor(private val userTracker: UserTracker) : AutoAddable {
+
+    private val spec = TileSpec.create(WorkModeTile.TILE_SPEC)
+
+    override fun autoAddSignal(userId: Int): Flow<AutoAddSignal> {
+        return conflatedCallbackFlow {
+            fun maybeSend(profiles: List<UserInfo>) {
+                if (profiles.any { it.id == userId }) {
+                    // We are looking at the profiles of the correct user.
+                    if (profiles.any { it.isManagedProfile }) {
+                        trySend(AutoAddSignal.Add(spec))
+                    } else {
+                        trySend(AutoAddSignal.Remove(spec))
+                    }
+                }
+            }
+
+            val callback =
+                object : UserTracker.Callback {
+                    override fun onProfilesChanged(profiles: List<UserInfo>) {
+                        maybeSend(profiles)
+                    }
+                }
+
+            userTracker.addCallback(callback) { it.run() }
+            maybeSend(userTracker.userProfiles)
+
+            awaitClose { userTracker.removeCallback(callback) }
+        }
+    }
+
+    override val autoAddTracking = AutoAddTracking.Always
+
+    override val description = "WorkTileAutoAddable ($autoAddTracking)"
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/interactor/AutoAddInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/interactor/AutoAddInteractor.kt
new file mode 100644
index 0000000..b747393
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/interactor/AutoAddInteractor.kt
@@ -0,0 +1,123 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.pipeline.domain.interactor
+
+import com.android.systemui.Dumpable
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dump.DumpManager
+import com.android.systemui.qs.pipeline.data.repository.AutoAddRepository
+import com.android.systemui.qs.pipeline.domain.model.AutoAddSignal
+import com.android.systemui.qs.pipeline.domain.model.AutoAddTracking
+import com.android.systemui.qs.pipeline.domain.model.AutoAddable
+import com.android.systemui.qs.pipeline.shared.logging.QSPipelineLogger
+import com.android.systemui.util.asIndenting
+import com.android.systemui.util.indentIfPossible
+import java.io.PrintWriter
+import java.util.concurrent.atomic.AtomicBoolean
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.coroutineScope
+import kotlinx.coroutines.flow.collectLatest
+import kotlinx.coroutines.flow.emptyFlow
+import kotlinx.coroutines.flow.filterIsInstance
+import kotlinx.coroutines.flow.merge
+import kotlinx.coroutines.flow.stateIn
+import kotlinx.coroutines.flow.take
+import kotlinx.coroutines.launch
+
+/**
+ * Collects the signals coming from all registered [AutoAddable] and adds/removes tiles accordingly.
+ */
+@SysUISingleton
+class AutoAddInteractor
+@Inject
+constructor(
+    private val autoAddables: Set<@JvmSuppressWildcards AutoAddable>,
+    private val repository: AutoAddRepository,
+    private val dumpManager: DumpManager,
+    private val qsPipelineLogger: QSPipelineLogger,
+    @Application private val scope: CoroutineScope,
+) : Dumpable {
+
+    private val initialized = AtomicBoolean(false)
+
+    /** Start collection of signals following the user from [currentTilesInteractor]. */
+    fun init(currentTilesInteractor: CurrentTilesInteractor) {
+        if (!initialized.compareAndSet(false, true)) {
+            return
+        }
+
+        dumpManager.registerNormalDumpable(TAG, this)
+
+        scope.launch {
+            currentTilesInteractor.userId.collectLatest { userId ->
+                coroutineScope {
+                    val previouslyAdded = repository.autoAddedTiles(userId).stateIn(this)
+
+                    autoAddables
+                        .map { addable ->
+                            val autoAddSignal = addable.autoAddSignal(userId)
+                            when (val lifecycle = addable.autoAddTracking) {
+                                is AutoAddTracking.Always -> autoAddSignal
+                                is AutoAddTracking.Disabled -> emptyFlow()
+                                is AutoAddTracking.IfNotAdded -> {
+                                    if (lifecycle.spec !in previouslyAdded.value) {
+                                        autoAddSignal.filterIsInstance<AutoAddSignal.Add>().take(1)
+                                    } else {
+                                        emptyFlow()
+                                    }
+                                }
+                            }
+                        }
+                        .merge()
+                        .collect { signal ->
+                            when (signal) {
+                                is AutoAddSignal.Add -> {
+                                    if (signal.spec !in previouslyAdded.value) {
+                                        currentTilesInteractor.addTile(signal.spec, signal.position)
+                                        qsPipelineLogger.logTileAutoAdded(
+                                            userId,
+                                            signal.spec,
+                                            signal.position
+                                        )
+                                        repository.markTileAdded(userId, signal.spec)
+                                    }
+                                }
+                                is AutoAddSignal.Remove -> {
+                                    currentTilesInteractor.removeTiles(setOf(signal.spec))
+                                    qsPipelineLogger.logTileAutoRemoved(userId, signal.spec)
+                                    repository.unmarkTileAdded(userId, signal.spec)
+                                }
+                            }
+                        }
+                }
+            }
+        }
+    }
+
+    override fun dump(pw: PrintWriter, args: Array<out String>) {
+        with(pw.asIndenting()) {
+            println("AutoAddables:")
+            indentIfPossible { autoAddables.forEach { println(it.description) } }
+        }
+    }
+
+    companion object {
+        private const val TAG = "AutoAddInteractor"
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/model/AutoAddSignal.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/model/AutoAddSignal.kt
new file mode 100644
index 0000000..ed7b8bd
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/model/AutoAddSignal.kt
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.pipeline.domain.model
+
+import com.android.systemui.qs.pipeline.data.repository.TileSpecRepository.Companion.POSITION_AT_END
+import com.android.systemui.qs.pipeline.shared.TileSpec
+
+/** Signal indicating when a tile needs to be auto-added or removed */
+sealed interface AutoAddSignal {
+    /** Tile for this object */
+    val spec: TileSpec
+
+    /** Signal for auto-adding a tile at [position]. */
+    data class Add(
+        override val spec: TileSpec,
+        val position: Int = POSITION_AT_END,
+    ) : AutoAddSignal
+
+    /** Signal for removing a tile. */
+    data class Remove(
+        override val spec: TileSpec,
+    ) : AutoAddSignal
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/model/AutoAddTracking.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/model/AutoAddTracking.kt
new file mode 100644
index 0000000..154d045
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/model/AutoAddTracking.kt
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.pipeline.domain.model
+
+import com.android.systemui.qs.pipeline.shared.TileSpec
+
+/** Strategy for when to track a particular [AutoAddable]. */
+sealed interface AutoAddTracking {
+
+    /**
+     * Indicates that the signals from the associated [AutoAddable] should all be collected and
+     * reacted accordingly. It may have [AutoAddSignal.Add] and [AutoAddSignal.Remove].
+     */
+    object Always : AutoAddTracking {
+        override fun toString(): String {
+            return "Always"
+        }
+    }
+
+    /**
+     * Indicates that the associated [AutoAddable] is [Disabled] and doesn't need to be collected.
+     */
+    object Disabled : AutoAddTracking {
+        override fun toString(): String {
+            return "Disabled"
+        }
+    }
+
+    /**
+     * Only the first [AutoAddSignal.Add] for each flow of signals needs to be collected, and only
+     * if the tile hasn't been auto-added yet. The associated [AutoAddable] will only emit
+     * [AutoAddSignal.Add].
+     */
+    data class IfNotAdded(val spec: TileSpec) : AutoAddTracking
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/model/AutoAddable.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/model/AutoAddable.kt
new file mode 100644
index 0000000..61fe5b4
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/model/AutoAddable.kt
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.pipeline.domain.model
+
+import kotlinx.coroutines.flow.Flow
+
+/**
+ * Tracks conditions for auto-adding or removing specific tiles.
+ *
+ * When creating a new [AutoAddable], it needs to be registered in a [Module] like
+ * [BaseAutoAddableModule], for example:
+ * ```
+ * @Binds
+ * @IntoSet
+ * fun providesMyAutoAddable(autoAddable: MyAutoAddable): AutoAddable
+ * ```
+ */
+interface AutoAddable {
+
+    /**
+     * Signals associated with a particular user indicating whether a particular tile needs to be
+     * auto-added or auto-removed.
+     */
+    fun autoAddSignal(userId: Int): Flow<AutoAddSignal>
+
+    /**
+     * Lifecycle for this object. It indicates in which cases [autoAddSignal] should be collected
+     */
+    val autoAddTracking: AutoAddTracking
+
+    /** Human readable description */
+    val description: String
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/startable/QSPipelineCoreStartable.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/startable/QSPipelineCoreStartable.kt
new file mode 100644
index 0000000..224fc1a
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/startable/QSPipelineCoreStartable.kt
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.pipeline.domain.startable
+
+import com.android.systemui.CoreStartable
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags
+import com.android.systemui.qs.pipeline.domain.interactor.AutoAddInteractor
+import com.android.systemui.qs.pipeline.domain.interactor.CurrentTilesInteractor
+import javax.inject.Inject
+
+@SysUISingleton
+class QSPipelineCoreStartable
+@Inject
+constructor(
+    private val currentTilesInteractor: CurrentTilesInteractor,
+    private val autoAddInteractor: AutoAddInteractor,
+    private val featureFlags: FeatureFlags,
+) : CoreStartable {
+
+    override fun start() {
+        if (
+            featureFlags.isEnabled(Flags.QS_PIPELINE_NEW_HOST) &&
+                featureFlags.isEnabled(Flags.QS_PIPELINE_AUTO_ADD)
+        ) {
+            autoAddInteractor.init(currentTilesInteractor)
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/prototyping/PrototypeCoreStartable.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/prototyping/PrototypeCoreStartable.kt
deleted file mode 100644
index bbd7234..0000000
--- a/packages/SystemUI/src/com/android/systemui/qs/pipeline/prototyping/PrototypeCoreStartable.kt
+++ /dev/null
@@ -1,120 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.qs.pipeline.prototyping
-
-import android.util.Log
-import com.android.systemui.CoreStartable
-import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.dagger.qualifiers.Application
-import com.android.systemui.flags.FeatureFlags
-import com.android.systemui.flags.Flags
-import com.android.systemui.qs.pipeline.data.repository.AutoAddRepository
-import com.android.systemui.qs.pipeline.data.repository.TileSpecRepository
-import com.android.systemui.qs.pipeline.shared.TileSpec
-import com.android.systemui.statusbar.commandline.Command
-import com.android.systemui.statusbar.commandline.CommandRegistry
-import com.android.systemui.user.data.repository.UserRepository
-import java.io.PrintWriter
-import javax.inject.Inject
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.flow.flatMapLatest
-import kotlinx.coroutines.launch
-
-/**
- * Class for observing results while prototyping.
- *
- * The flows do their own logging, so we just need to make sure that they collect.
- *
- * This will be torn down together with the last of the new pipeline flags remaining here.
- */
-// TODO(b/270385608)
-@SysUISingleton
-class PrototypeCoreStartable
-@Inject
-constructor(
-    private val tileSpecRepository: TileSpecRepository,
-    private val autoAddRepository: AutoAddRepository,
-    private val userRepository: UserRepository,
-    private val featureFlags: FeatureFlags,
-    @Application private val scope: CoroutineScope,
-    private val commandRegistry: CommandRegistry,
-) : CoreStartable {
-
-    @OptIn(ExperimentalCoroutinesApi::class)
-    override fun start() {
-        if (featureFlags.isEnabled(Flags.QS_PIPELINE_NEW_HOST)) {
-            scope.launch {
-                userRepository.selectedUserInfo
-                    .flatMapLatest { user -> tileSpecRepository.tilesSpecs(user.id) }
-                    .collect {}
-            }
-            if (featureFlags.isEnabled(Flags.QS_PIPELINE_AUTO_ADD)) {
-                scope.launch {
-                    userRepository.selectedUserInfo
-                        .flatMapLatest { user -> autoAddRepository.autoAddedTiles(user.id) }
-                        .collect { tiles -> Log.d(TAG, "Auto-added tiles: $tiles") }
-                }
-            }
-            commandRegistry.registerCommand(COMMAND, ::CommandExecutor)
-        }
-    }
-
-    private inner class CommandExecutor : Command {
-        override fun execute(pw: PrintWriter, args: List<String>) {
-            if (args.size < 2) {
-                pw.println("Error: needs at least two arguments")
-                return
-            }
-            val spec = TileSpec.create(args[1])
-            if (spec == TileSpec.Invalid) {
-                pw.println("Error: Invalid tile spec ${args[1]}")
-            }
-            if (args[0] == "add") {
-                performAdd(args, spec)
-                pw.println("Requested tile added")
-            } else if (args[0] == "remove") {
-                performRemove(args, spec)
-                pw.println("Requested tile removed")
-            } else {
-                pw.println("Error: unknown command")
-            }
-        }
-
-        private fun performAdd(args: List<String>, spec: TileSpec) {
-            val position = args.getOrNull(2)?.toInt() ?: TileSpecRepository.POSITION_AT_END
-            val user = args.getOrNull(3)?.toInt() ?: userRepository.getSelectedUserInfo().id
-            scope.launch { tileSpecRepository.addTile(user, spec, position) }
-        }
-
-        private fun performRemove(args: List<String>, spec: TileSpec) {
-            val user = args.getOrNull(2)?.toInt() ?: userRepository.getSelectedUserInfo().id
-            scope.launch { tileSpecRepository.removeTiles(user, listOf(spec)) }
-        }
-
-        override fun help(pw: PrintWriter) {
-            pw.println("Usage: adb shell cmd statusbar $COMMAND:")
-            pw.println("  add <spec> [position] [user]")
-            pw.println("  remove <spec> [user]")
-        }
-    }
-
-    companion object {
-        private const val COMMAND = "qs-pipeline"
-        private const val TAG = "PrototypeCoreStartable"
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/shared/TileSpec.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/shared/TileSpec.kt
index af1cd09..11b5dd7 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/pipeline/shared/TileSpec.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/shared/TileSpec.kt
@@ -52,7 +52,11 @@
     internal constructor(
         override val spec: String,
         val componentName: ComponentName,
-    ) : TileSpec(spec)
+    ) : TileSpec(spec) {
+        override fun toString(): String {
+            return "CustomTileSpec(${componentName.toShortString()})"
+        }
+    }
 
     companion object {
         /** Create a [TileSpec] from the string [spec]. */
diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/shared/logging/QSPipelineLogger.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/shared/logging/QSPipelineLogger.kt
index 8318ec9..573cb715 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/pipeline/shared/logging/QSPipelineLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/shared/logging/QSPipelineLogger.kt
@@ -18,7 +18,8 @@
 
 import android.annotation.UserIdInt
 import com.android.systemui.log.LogBuffer
-import com.android.systemui.log.LogLevel
+import com.android.systemui.log.core.LogLevel
+import com.android.systemui.qs.pipeline.dagger.QSAutoAddLog
 import com.android.systemui.qs.pipeline.dagger.QSTileListLog
 import com.android.systemui.qs.pipeline.shared.TileSpec
 import javax.inject.Inject
@@ -32,10 +33,12 @@
 @Inject
 constructor(
     @QSTileListLog private val tileListLogBuffer: LogBuffer,
+    @QSAutoAddLog private val tileAutoAddLogBuffer: LogBuffer,
 ) {
 
     companion object {
         const val TILE_LIST_TAG = "QSTileListLog"
+        const val AUTO_ADD_TAG = "QSAutoAddableLog"
     }
 
     /**
@@ -136,6 +139,31 @@
         )
     }
 
+    fun logTileAutoAdded(userId: Int, spec: TileSpec, position: Int) {
+        tileAutoAddLogBuffer.log(
+            AUTO_ADD_TAG,
+            LogLevel.DEBUG,
+            {
+                int1 = userId
+                int2 = position
+                str1 = spec.toString()
+            },
+            { "Tile $str1 auto added for user $int1 at position $int2" }
+        )
+    }
+
+    fun logTileAutoRemoved(userId: Int, spec: TileSpec) {
+        tileAutoAddLogBuffer.log(
+            AUTO_ADD_TAG,
+            LogLevel.DEBUG,
+            {
+                int1 = userId
+                str1 = spec.toString()
+            },
+            { "Tile $str1 auto removed for user $int1" }
+        )
+    }
+
     /** Reasons for destroying an existing tile. */
     enum class TileDestroyedReason(val readable: String) {
         TILE_REMOVED("Tile removed from  current set"),
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSIconViewImpl.java b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSIconViewImpl.java
index e541681..7e45491 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSIconViewImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSIconViewImpl.java
@@ -248,13 +248,11 @@
      */
     private static int getIconColorForState(Context context, QSTile.State state) {
         if (state.disabledByPolicy || state.state == Tile.STATE_UNAVAILABLE) {
-            return Utils.getColorAttrDefaultColor(
-                    context, com.android.internal.R.attr.textColorTertiary);
+            return Utils.getColorAttrDefaultColor(context, R.attr.outline);
         } else if (state.state == Tile.STATE_INACTIVE) {
-            return Utils.getColorAttrDefaultColor(context, android.R.attr.textColorPrimary);
+            return Utils.getColorAttrDefaultColor(context, R.attr.onShadeInactiveVariant);
         } else if (state.state == Tile.STATE_ACTIVE) {
-            return Utils.getColorAttrDefaultColor(context,
-                    com.android.internal.R.attr.textColorOnAccent);
+            return Utils.getColorAttrDefaultColor(context, R.attr.onShadeActive);
         } else {
             Log.e("QSIconView", "Invalid state " + state);
             return 0;
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt
index b806683..d81e4c2 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt
@@ -29,6 +29,7 @@
 import android.service.quicksettings.Tile
 import android.text.TextUtils
 import android.util.Log
+import android.util.TypedValue
 import android.view.Gravity
 import android.view.LayoutInflater
 import android.view.View
@@ -92,24 +93,21 @@
             updateHeight()
         }
 
-    private val colorActive = Utils.getColorAttrDefaultColor(context,
-            com.android.internal.R.attr.colorAccentPrimary)
-    private val colorInactive = Utils.getColorAttrDefaultColor(context, R.attr.offStateColor)
-    private val colorUnavailable = Utils.applyAlpha(UNAVAILABLE_ALPHA, colorInactive)
+    private val colorActive = Utils.getColorAttrDefaultColor(context, R.attr.shadeActive)
+    private val colorInactive = Utils.getColorAttrDefaultColor(context, R.attr.shadeInactive)
+    private val colorUnavailable = Utils.getColorAttrDefaultColor(context, R.attr.shadeDisabled)
 
-    private val colorLabelActive =
-            Utils.getColorAttrDefaultColor(context, com.android.internal.R.attr.textColorOnAccent)
-    private val colorLabelInactive =
-            Utils.getColorAttrDefaultColor(context, android.R.attr.textColorPrimary)
+    private val colorLabelActive = Utils.getColorAttrDefaultColor(context, R.attr.onShadeActive)
+    private val colorLabelInactive = Utils.getColorAttrDefaultColor(context, R.attr.onShadeInactive)
     private val colorLabelUnavailable =
-        Utils.getColorAttrDefaultColor(context, com.android.internal.R.attr.textColorTertiary)
+        Utils.getColorAttrDefaultColor(context, R.attr.outline)
 
     private val colorSecondaryLabelActive =
-            Utils.getColorAttrDefaultColor(context, android.R.attr.textColorSecondaryInverse)
+        Utils.getColorAttrDefaultColor(context, R.attr.onShadeActiveVariant)
     private val colorSecondaryLabelInactive =
-            Utils.getColorAttrDefaultColor(context, android.R.attr.textColorSecondary)
+            Utils.getColorAttrDefaultColor(context, R.attr.onShadeInactiveVariant)
     private val colorSecondaryLabelUnavailable =
-        Utils.getColorAttrDefaultColor(context, com.android.internal.R.attr.textColorTertiary)
+        Utils.getColorAttrDefaultColor(context, R.attr.outline)
 
     private lateinit var label: TextView
     protected lateinit var secondaryLabel: TextView
@@ -151,6 +149,11 @@
     private val locInScreen = IntArray(2)
 
     init {
+        val typedValue = TypedValue()
+        if (!getContext().theme.resolveAttribute(R.attr.isQsTheme, typedValue, true)) {
+            throw IllegalStateException("QSViewImpl must be inflated with a theme that contains " +
+                    "Theme.SystemUI.QuickSettings")
+        }
         setId(generateViewId())
         orientation = LinearLayout.HORIZONTAL
         gravity = Gravity.CENTER_VERTICAL or Gravity.START
diff --git a/packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneWindowRootView.kt b/packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneWindowRootView.kt
index 9b5898f..2ad5429 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneWindowRootView.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneWindowRootView.kt
@@ -52,9 +52,8 @@
                                     setOnBackInvokedDispatcher(viewRootImpl.onBackInvokedDispatcher)
                                 }
 
-                            override fun getLifecycle(): Lifecycle {
-                                return this@repeatWhenAttached.lifecycle
-                            }
+                            override val lifecycle: Lifecycle =
+                                this@repeatWhenAttached.lifecycle
                         }
                     )
 
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
index 2ef9e07..44436b9 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
@@ -117,6 +117,7 @@
 import com.android.systemui.bouncer.shared.constants.KeyguardBouncerConstants;
 import com.android.systemui.classifier.Classifier;
 import com.android.systemui.classifier.FalsingCollector;
+import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dagger.qualifiers.DisplayId;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.doze.DozeLog;
@@ -208,7 +209,6 @@
 import com.android.systemui.statusbar.phone.StatusBarTouchableRegionManager;
 import com.android.systemui.statusbar.phone.TapAgainViewController;
 import com.android.systemui.statusbar.phone.UnlockedScreenOffAnimationController;
-import com.android.systemui.statusbar.phone.dagger.CentralSurfacesComponent;
 import com.android.systemui.statusbar.phone.fragment.CollapsedStatusBarFragment;
 import com.android.systemui.statusbar.policy.ConfigurationController;
 import com.android.systemui.statusbar.policy.KeyguardQsUserSwitchController;
@@ -238,7 +238,7 @@
 
 import kotlinx.coroutines.CoroutineDispatcher;
 
-@CentralSurfacesComponent.CentralSurfacesScope
+@SysUISingleton
 public final class NotificationPanelViewController implements ShadeSurface, Dumpable {
 
     public static final String TAG = NotificationPanelView.class.getSimpleName();
@@ -1407,11 +1407,13 @@
         mKeyguardBottomArea = keyguardBottomArea;
     }
 
-    void setOpenCloseListener(OpenCloseListener openCloseListener) {
+    @Override
+    public void setOpenCloseListener(OpenCloseListener openCloseListener) {
         mOpenCloseListener = openCloseListener;
     }
 
-    void setTrackingStartedListener(TrackingStartedListener trackingStartedListener) {
+    @Override
+    public void setTrackingStartedListener(TrackingStartedListener trackingStartedListener) {
         mTrackingStartedListener = trackingStartedListener;
     }
 
@@ -3378,11 +3380,13 @@
         ViewGroupFadeHelper.reset(mView);
     }
 
-    void addOnGlobalLayoutListener(ViewTreeObserver.OnGlobalLayoutListener listener) {
+    @Override
+    public void addOnGlobalLayoutListener(ViewTreeObserver.OnGlobalLayoutListener listener) {
         mView.getViewTreeObserver().addOnGlobalLayoutListener(listener);
     }
 
-    void removeOnGlobalLayoutListener(ViewTreeObserver.OnGlobalLayoutListener listener) {
+    @Override
+    public void removeOnGlobalLayoutListener(ViewTreeObserver.OnGlobalLayoutListener listener) {
         mView.getViewTreeObserver().removeOnGlobalLayoutListener(listener);
     }
 
@@ -3565,6 +3569,7 @@
     }
 
     private void endMotionEvent(MotionEvent event, float x, float y, boolean forceCancel) {
+        mShadeLog.logEndMotionEvent("endMotionEvent called", forceCancel, false);
         mTrackingPointer = -1;
         mAmbientState.setSwipingUp(false);
         if ((mTracking && mTouchSlopExceeded) || Math.abs(x - mInitialExpandX) > mTouchSlop
@@ -3586,15 +3591,19 @@
             } else if (event.getActionMasked() == MotionEvent.ACTION_CANCEL || forceCancel) {
                 if (onKeyguard) {
                     expand = true;
+                    mShadeLog.logEndMotionEvent("endMotionEvent: cancel while on keyguard",
+                            forceCancel, expand);
                 } else if (mCentralSurfaces.isBouncerShowingOverDream()) {
                     expand = false;
                 } else {
                     // If we get a cancel, put the shade back to the state it was in when the
                     // gesture started
                     expand = !mPanelClosedOnDown;
+                    mShadeLog.logEndMotionEvent("endMotionEvent: cancel", forceCancel, expand);
                 }
             } else {
                 expand = flingExpands(vel, vectorVel, x, y);
+                mShadeLog.logEndMotionEvent("endMotionEvent: flingExpands", forceCancel, expand);
             }
 
             mDozeLog.traceFling(
@@ -3847,8 +3856,8 @@
         return !isFullyCollapsed() && !mTracking && !mClosing;
     }
 
-    /** Collapses the shade instantly without animation. */
-    void instantCollapse() {
+    @Override
+    public void instantCollapse() {
         abortAnimations();
         setExpandedFraction(0f);
         if (mExpanding) {
@@ -4021,8 +4030,8 @@
         mFixedDuration = NO_FIXED_DURATION;
     }
 
-    /** */
-    boolean postToView(Runnable action) {
+    @Override
+    public boolean postToView(Runnable action) {
         return mView.post(action);
     }
 
@@ -4731,6 +4740,8 @@
                     mTouchSlopExceeded = mTouchSlopExceededBeforeDown;
                     mMotionAborted = false;
                     mPanelClosedOnDown = isFullyCollapsed();
+                    mShadeLog.logPanelClosedOnDown("intercept down touch", mPanelClosedOnDown,
+                            mExpandedFraction);
                     mCollapsedAndHeadsUpOnDown = false;
                     mHasLayoutedSinceDown = false;
                     mUpdateFlingOnLayout = false;
@@ -4948,6 +4959,8 @@
                     startExpandMotion(x, y, false /* startTracking */, mExpandedHeight);
                     mMinExpandHeight = 0.0f;
                     mPanelClosedOnDown = isFullyCollapsed();
+                    mShadeLog.logPanelClosedOnDown("handle down touch", mPanelClosedOnDown,
+                            mExpandedFraction);
                     mHasLayoutedSinceDown = false;
                     mUpdateFlingOnLayout = false;
                     mMotionAborted = false;
@@ -5113,18 +5126,5 @@
             return super.performAccessibilityAction(host, action, args);
         }
     }
-
-    /** Listens for when touch tracking begins. */
-    interface TrackingStartedListener {
-        void onTrackingStarted();
-    }
-
-    /** Listens for when shade begins opening of finishes closing. */
-    interface OpenCloseListener {
-        /** Called when the shade finishes closing. */
-        void onClosingFinished();
-        /** Called when the shade starts opening. */
-        void onOpenStarted();
-    }
 }
 
diff --git a/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java b/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java
index 1361c9f..fe1b365 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java
@@ -68,6 +68,7 @@
 import com.android.systemui.R;
 import com.android.systemui.classifier.Classifier;
 import com.android.systemui.classifier.FalsingCollector;
+import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.flags.FeatureFlags;
 import com.android.systemui.fragments.FragmentHostManager;
@@ -98,7 +99,6 @@
 import com.android.systemui.statusbar.phone.ScrimController;
 import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
 import com.android.systemui.statusbar.phone.StatusBarTouchableRegionManager;
-import com.android.systemui.statusbar.phone.dagger.CentralSurfacesComponent;
 import com.android.systemui.statusbar.policy.CastController;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
 import com.android.systemui.util.LargeScreenUtils;
@@ -113,7 +113,7 @@
 /** Handles QuickSettings touch handling, expansion and animation state
  * TODO (b/264460656) make this dumpable
  */
-@CentralSurfacesComponent.CentralSurfacesScope
+@SysUISingleton
 public class QuickSettingsController implements Dumpable {
     public static final String TAG = "QuickSettingsController";
 
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeController.java b/packages/SystemUI/src/com/android/systemui/shade/ShadeController.java
index 9ed0e9a..317d885 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeController.java
@@ -165,8 +165,7 @@
             NotificationShadeWindowViewController notificationShadeWindowViewController);
 
     /** */
-    void setNotificationPanelViewController(
-            NotificationPanelViewController notificationPanelViewController);
+    void setShadeViewController(ShadeViewController shadeViewController);
 
     /** Listens for shade visibility changes. */
     interface ShadeVisibilityListener {
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerImpl.java b/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerImpl.java
index c9338b3..b92afac 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerImpl.java
@@ -70,7 +70,8 @@
 
     private boolean mExpandedVisible;
 
-    private NotificationPanelViewController mNotificationPanelViewController;
+    // TODO(b/237661616): Rename this variable to mShadeViewController.
+    private ShadeViewController mNotificationPanelViewController;
     private NotificationPresenter mPresenter;
     private NotificationShadeWindowViewController mNotificationShadeWindowViewController;
     private ShadeVisibilityListener mShadeVisibilityListener;
@@ -426,12 +427,11 @@
     }
 
     @Override
-    public void setNotificationPanelViewController(
-            NotificationPanelViewController notificationPanelViewController) {
-        mNotificationPanelViewController = notificationPanelViewController;
+    public void setShadeViewController(ShadeViewController shadeViewController) {
+        mNotificationPanelViewController = shadeViewController;
         mNotificationPanelViewController.setTrackingStartedListener(this::runPostCollapseRunnables);
         mNotificationPanelViewController.setOpenCloseListener(
-                new NotificationPanelViewController.OpenCloseListener() {
+                new OpenCloseListener() {
                     @Override
                     public void onClosingFinished() {
                         ShadeControllerImpl.this.onClosingFinished();
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeHeaderController.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeHeaderController.kt
index 3af75ce..8789a8b 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeHeaderController.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeHeaderController.kt
@@ -195,7 +195,9 @@
         set(value) {
             if (visible && field != value) {
                 field = value
+                iconContainer.setQsExpansionTransitioning(value > 0f && value < 1.0f)
                 updatePosition()
+                updateIgnoredSlots()
             }
         }
 
@@ -216,6 +218,8 @@
             view.onApplyWindowInsets(insets)
         }
 
+    private var singleCarrier = false
+
     private val demoModeReceiver =
         object : DemoMode {
             override fun demoCommands() = listOf(DemoMode.COMMAND_CLOCK)
@@ -479,17 +483,20 @@
     private fun updateListeners() {
         mShadeCarrierGroupController.setListening(visible)
         if (visible) {
-            updateSingleCarrier(mShadeCarrierGroupController.isSingleCarrier)
+            singleCarrier = mShadeCarrierGroupController.isSingleCarrier
+            updateIgnoredSlots()
             mShadeCarrierGroupController.setOnSingleCarrierChangedListener {
-                updateSingleCarrier(it)
+                singleCarrier = it
+                updateIgnoredSlots()
             }
         } else {
             mShadeCarrierGroupController.setOnSingleCarrierChangedListener(null)
         }
     }
 
-    private fun updateSingleCarrier(singleCarrier: Boolean) {
-        if (singleCarrier) {
+    private fun updateIgnoredSlots() {
+        // switching from QQS to QS state halfway through the transition
+        if (singleCarrier || qsExpandedFraction < 0.5) {
             iconContainer.removeIgnoredSlots(carrierIconSlots)
         } else {
             iconContainer.addIgnoredSlots(carrierIconSlots)
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeLogger.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeLogger.kt
index 2da8d5f4..1c30bdd 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeLogger.kt
@@ -19,7 +19,7 @@
 import android.view.MotionEvent
 import com.android.systemui.log.dagger.ShadeLog
 import com.android.systemui.log.LogBuffer
-import com.android.systemui.log.LogLevel
+import com.android.systemui.log.core.LogLevel
 import com.android.systemui.shade.ShadeViewController.Companion.FLING_COLLAPSE
 import com.android.systemui.shade.ShadeViewController.Companion.FLING_EXPAND
 import com.android.systemui.shade.ShadeViewController.Companion.FLING_HIDE
@@ -90,7 +90,7 @@
                 double1 = event.y.toDouble()
             },
             {
-                "$str1\neventTime=$long1,downTime=$long2,y=$double1,action=$int1,class=$int2"
+                "$str1: eventTime=$long1,downTime=$long2,y=$double1,action=$int1,class=$int2"
             }
         )
     }
@@ -280,6 +280,42 @@
         )
     }
 
+    fun logEndMotionEvent(
+        msg: String,
+        forceCancel: Boolean,
+        expand: Boolean,
+    )
+    {
+        buffer.log(
+            TAG,
+            LogLevel.VERBOSE,
+            {
+                str1 = msg
+                bool1 = forceCancel
+                bool2 = expand
+            },
+            { "$str1; force=$bool1; expand=$bool2" }
+        )
+    }
+
+    fun logPanelClosedOnDown(
+        msg: String,
+        panelClosedOnDown: Boolean,
+        expandFraction: Float,
+    )
+    {
+        buffer.log(
+            TAG,
+            LogLevel.VERBOSE,
+            {
+                str1 = msg
+                bool1 = panelClosedOnDown
+                double1 = expandFraction.toDouble()
+            },
+            { "$str1; mPanelClosedOnDown=$bool1; mExpandedFraction=$double1" }
+        )
+    }
+
     fun flingQs(flingType: Int, isClick: Boolean) {
         buffer.log(
             TAG,
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeModule.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeModule.kt
index a2b9351..0500a58 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeModule.kt
@@ -49,6 +49,7 @@
 import com.android.systemui.statusbar.notification.row.dagger.NotificationShelfComponent
 import com.android.systemui.statusbar.notification.shelf.ui.viewbinder.NotificationShelfViewBinderWrapperControllerImpl
 import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout
+import com.android.systemui.statusbar.phone.KeyguardBottomAreaView
 import com.android.systemui.statusbar.phone.StatusIconContainer
 import com.android.systemui.statusbar.phone.TapAgainView
 import com.android.systemui.statusbar.policy.BatteryController
@@ -71,6 +72,12 @@
     @ClassKey(AuthRippleController::class)
     abstract fun bindAuthRippleController(controller: AuthRippleController): CoreStartable
 
+    @Binds
+    @SysUISingleton
+    abstract fun bindsShadeViewController(
+        notificationPanelViewController: NotificationPanelViewController
+    ): ShadeViewController
+
     companion object {
         const val SHADE_HEADER = "large_screen_shade_header"
 
@@ -165,6 +172,20 @@
             return notificationShadeWindowView.findViewById(R.id.notification_panel)
         }
 
+        /**
+         * Constructs a new, unattached [KeyguardBottomAreaView].
+         *
+         * Note that this is explicitly _not_ a singleton, as we want to be able to reinflate it
+         */
+        @Provides
+        fun providesKeyguardBottomAreaView(
+            npv: NotificationPanelView,
+            layoutInflater: LayoutInflater,
+        ): KeyguardBottomAreaView {
+            return layoutInflater.inflate(R.layout.keyguard_bottom_area, npv, false)
+                as KeyguardBottomAreaView
+        }
+
         @Provides
         @SysUISingleton
         fun providesLightRevealScrim(
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeViewController.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeViewController.kt
index 3d9fcf9..9aa5eb0 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeViewController.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeViewController.kt
@@ -17,6 +17,7 @@
 
 import android.view.MotionEvent
 import android.view.ViewGroup
+import android.view.ViewTreeObserver
 import com.android.systemui.keyguard.shared.model.WakefulnessModel
 import com.android.systemui.statusbar.RemoteInputController
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
@@ -77,6 +78,9 @@
     /** Collapses the shade with an animation duration in milliseconds. */
     fun collapseWithDuration(animationDuration: Int)
 
+    /** Collapses the shade instantly without animation. */
+    fun instantCollapse()
+
     /**
      * Animate QS collapse by flinging it. If QS is expanded, it will collapse into QQS and stop. If
      * in split shade, it will collapse the whole shade.
@@ -100,6 +104,9 @@
     /** Returns whether the shade's top level view is enabled. */
     val isViewEnabled: Boolean
 
+    /** Sets a listener to be notified when the shade starts opening or finishes closing. */
+    fun setOpenCloseListener(openCloseListener: OpenCloseListener)
+
     /** Returns whether status bar icons should be hidden when the shade is expanded. */
     fun shouldHideStatusBarIconsWhenExpanded(): Boolean
 
@@ -109,6 +116,9 @@
      */
     fun blockExpansionForCurrentTouch()
 
+    /** Sets a listener to be notified when touch tracking begins. */
+    fun setTrackingStartedListener(trackingStartedListener: TrackingStartedListener)
+
     /**
      * Disables the shade header.
      *
@@ -178,6 +188,15 @@
     /** Ensures that the touchable region is updated. */
     fun updateTouchableRegion()
 
+    /** Adds a global layout listener. */
+    fun addOnGlobalLayoutListener(listener: ViewTreeObserver.OnGlobalLayoutListener)
+
+    /** Removes a global layout listener. */
+    fun removeOnGlobalLayoutListener(listener: ViewTreeObserver.OnGlobalLayoutListener)
+
+    /** Posts the given runnable to the view. */
+    fun postToView(action: Runnable): Boolean
+
     // ******* Begin Keyguard Section *********
     /** Animate to expanded shade after a delay in ms. Used for lockscreen to shade transition. */
     fun transitionToExpandedShade(delay: Long)
@@ -337,3 +356,17 @@
     /** Return the fraction of the shade that's expanded, when in lockscreen. */
     val lockscreenShadeDragProgress: Float
 }
+
+/** Listens for when touch tracking begins. */
+interface TrackingStartedListener {
+    fun onTrackingStarted()
+}
+
+/** Listens for when shade begins opening or finishes closing. */
+interface OpenCloseListener {
+    /** Called when the shade finishes closing. */
+    fun onClosingFinished()
+
+    /** Called when the shade starts opening. */
+    fun onOpenStarted()
+}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeWindowLogger.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeWindowLogger.kt
index d06634b..51a27cf 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeWindowLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeWindowLogger.kt
@@ -21,9 +21,9 @@
 import com.android.systemui.log.ConstantStringsLogger
 import com.android.systemui.log.ConstantStringsLoggerImpl
 import com.android.systemui.log.LogBuffer
-import com.android.systemui.log.LogLevel
-import com.android.systemui.log.LogLevel.DEBUG
-import com.android.systemui.log.LogMessage
+import com.android.systemui.log.core.LogLevel
+import com.android.systemui.log.core.LogLevel.DEBUG
+import com.android.systemui.log.core.LogMessage
 import javax.inject.Inject
 
 private const val TAG = "systemui.shadewindow"
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ActionClickLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/ActionClickLogger.kt
index e008ec0..d3c19b7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/ActionClickLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/ActionClickLogger.kt
@@ -19,7 +19,7 @@
 import android.app.PendingIntent
 import com.android.systemui.log.dagger.NotifInteractionLog
 import com.android.systemui.log.LogBuffer
-import com.android.systemui.log.LogLevel
+import com.android.systemui.log.core.LogLevel
 import com.android.systemui.statusbar.notification.collection.NotificationEntry
 import javax.inject.Inject
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
index 6c2c0cf..a532195 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
@@ -19,6 +19,7 @@
 import static android.app.StatusBarManager.DISABLE2_NONE;
 import static android.app.StatusBarManager.DISABLE_NONE;
 import static android.inputmethodservice.InputMethodService.BACK_DISPOSITION_DEFAULT;
+import static android.inputmethodservice.InputMethodService.IME_INVISIBLE;
 import static android.view.Display.INVALID_DISPLAY;
 
 import android.annotation.Nullable;
@@ -36,7 +37,7 @@
 import android.hardware.biometrics.IBiometricSysuiReceiver;
 import android.hardware.biometrics.PromptInfo;
 import android.hardware.fingerprint.IUdfpsRefreshRateRequestCallback;
-import android.inputmethodservice.InputMethodService;
+import android.inputmethodservice.InputMethodService.BackDispositionMode;
 import android.media.INearbyMediaDevicesProvider;
 import android.media.MediaRoute2Info;
 import android.os.Binder;
@@ -225,10 +226,8 @@
          * @param backDisposition Disposition mode of back button. It should be one of below flags:
          * @param showImeSwitcher {@code true} to show IME switch button.
          */
-        default void setImeWindowStatus(int displayId, IBinder token,
-                @InputMethodService.ImeWindowVisibility int vis,
-                @InputMethodService.BackDispositionMode int backDisposition,
-                boolean showImeSwitcher) { }
+        default void setImeWindowStatus(int displayId, IBinder token,  int vis,
+                @BackDispositionMode int backDisposition, boolean showImeSwitcher) { }
         default void showRecentApps(boolean triggeredFromAltTab) { }
         default void hideRecentApps(boolean triggeredFromAltTab, boolean triggeredFromHomeKey) { }
         default void toggleTaskbar() { }
@@ -679,9 +678,7 @@
     }
 
     @Override
-    public void setImeWindowStatus(int displayId, IBinder token,
-            @InputMethodService.ImeWindowVisibility int vis,
-            @InputMethodService.BackDispositionMode int backDisposition,
+    public void setImeWindowStatus(int displayId, IBinder token, int vis, int backDisposition,
             boolean showImeSwitcher) {
         synchronized (mLock) {
             mHandler.removeMessages(MSG_SHOW_IME_BUTTON);
@@ -1095,9 +1092,7 @@
         }
     }
 
-    private void handleShowImeButton(int displayId, IBinder token,
-            @InputMethodService.ImeWindowVisibility int vis,
-            @InputMethodService.BackDispositionMode int backDisposition,
+    private void handleShowImeButton(int displayId, IBinder token, int vis, int backDisposition,
             boolean showImeSwitcher) {
         if (displayId == INVALID_DISPLAY) return;
 
@@ -1117,7 +1112,7 @@
     private void sendImeInvisibleStatusForPrevNavBar() {
         for (int i = 0; i < mCallbacks.size(); i++) {
             mCallbacks.get(i).setImeWindowStatus(mLastUpdatedImeDisplayId,
-                    null /* token */, 0 /* vis */, BACK_DISPOSITION_DEFAULT,
+                    null /* token */, IME_INVISIBLE, BACK_DISPOSITION_DEFAULT,
                     false /* showImeSwitcher */);
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcutListSearch.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcutListSearch.java
index 3918144..ec66e99 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcutListSearch.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcutListSearch.java
@@ -557,19 +557,7 @@
                 new ShortcutKeyGroupMultiMappingInfo(
                         context.getString(R.string.group_system_access_google_assistant),
                         Arrays.asList(
-                                Pair.create(KeyEvent.KEYCODE_A, KeyEvent.META_META_ON))),
-                /*  Lock screen: Meta + L */
-                new ShortcutKeyGroupMultiMappingInfo(
-                        context.getString(R.string.group_system_lock_screen),
-                        Arrays.asList(
-                                Pair.create(KeyEvent.KEYCODE_L, KeyEvent.META_META_ON))),
-                /* Pull up Notes app for quick memo: Meta + Ctrl + N */
-                new ShortcutKeyGroupMultiMappingInfo(
-                        context.getString(R.string.group_system_quick_memo),
-                        Arrays.asList(
-                                Pair.create(
-                                        KeyEvent.KEYCODE_N,
-                                        KeyEvent.META_META_ON | KeyEvent.META_CTRL_ON)))
+                                Pair.create(KeyEvent.KEYCODE_A, KeyEvent.META_META_ON)))
         );
         for (ShortcutKeyGroupMultiMappingInfo info : infoList) {
             systemGroup.addItem(info.getShortcutMultiMappingInfo());
@@ -611,21 +599,12 @@
                         new ArrayList<>());
 
         // System multitasking shortcuts:
-        //    Enter Split screen with current app to RHS: Meta + Ctrl + Right arrow
-        //    Enter Split screen with current app to LHS: Meta + Ctrl + Left arrow
         //    Switch from Split screen to full screen: Meta + Ctrl + Up arrow
-        //    During Split screen: replace an app from one to another: Meta + Ctrl + Down arrow
         String[] shortcutLabels = {
-                context.getString(R.string.system_multitasking_rhs),
-                context.getString(R.string.system_multitasking_lhs),
                 context.getString(R.string.system_multitasking_full_screen),
-                context.getString(R.string.system_multitasking_replace)
         };
         int[] keyCodes = {
-                KeyEvent.KEYCODE_DPAD_RIGHT,
-                KeyEvent.KEYCODE_DPAD_LEFT,
                 KeyEvent.KEYCODE_DPAD_UP,
-                KeyEvent.KEYCODE_DPAD_DOWN
         };
 
         for (int i = 0; i < shortcutLabels.length; i++) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
index 9692482..42ebaa3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
@@ -41,7 +41,7 @@
 import static com.android.systemui.keyguard.KeyguardIndicationRotateTextViewController.INDICATION_TYPE_TRUST;
 import static com.android.systemui.keyguard.KeyguardIndicationRotateTextViewController.INDICATION_TYPE_USER_LOCKED;
 import static com.android.systemui.keyguard.ScreenLifecycle.SCREEN_ON;
-import static com.android.systemui.log.LogLevel.ERROR;
+import static com.android.systemui.log.core.LogLevel.ERROR;
 import static com.android.systemui.plugins.FalsingManager.LOW_PENALTY;
 
 import android.app.AlarmManager;
@@ -97,7 +97,7 @@
 import com.android.systemui.keyguard.KeyguardIndicationRotateTextViewController;
 import com.android.systemui.keyguard.ScreenLifecycle;
 import com.android.systemui.keyguard.util.IndicationHelper;
-import com.android.systemui.log.LogLevel;
+import com.android.systemui.log.core.LogLevel;
 import com.android.systemui.plugins.FalsingManager;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.settings.UserTracker;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/QsFrameTranslateController.java b/packages/SystemUI/src/com/android/systemui/statusbar/QsFrameTranslateController.java
index 59afb18..9702bfc 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/QsFrameTranslateController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/QsFrameTranslateController.java
@@ -18,19 +18,19 @@
 
 import android.view.View;
 
+import com.android.systemui.display.data.repository.DisplayMetricsRepository;
 import com.android.systemui.plugins.qs.QS;
 import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController;
-import com.android.systemui.statusbar.phone.CentralSurfaces;
 
 /**
  * Calculates and moves the QS frame vertically.
  */
 public abstract class QsFrameTranslateController {
 
-    protected CentralSurfaces mCentralSurfaces;
+    protected DisplayMetricsRepository mDisplayMetricsRepository;
 
-    public QsFrameTranslateController(CentralSurfaces centralSurfaces) {
-        mCentralSurfaces = centralSurfaces;
+    public QsFrameTranslateController(DisplayMetricsRepository displayMetricsRepository) {
+        mDisplayMetricsRepository = displayMetricsRepository;
     }
 
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/QsFrameTranslateImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/QsFrameTranslateImpl.java
index 85b522c..e429b8b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/QsFrameTranslateImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/QsFrameTranslateImpl.java
@@ -19,9 +19,9 @@
 import android.view.View;
 
 import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.display.data.repository.DisplayMetricsRepository;
 import com.android.systemui.plugins.qs.QS;
 import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController;
-import com.android.systemui.statusbar.phone.CentralSurfaces;
 
 import javax.inject.Inject;
 
@@ -34,8 +34,8 @@
 public class QsFrameTranslateImpl extends QsFrameTranslateController {
 
     @Inject
-    public QsFrameTranslateImpl(CentralSurfaces centralSurfaces) {
-        super(centralSurfaces);
+    public QsFrameTranslateImpl(DisplayMetricsRepository displayMetricsRepository) {
+        super(displayMetricsRepository);
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/NetworkControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/NetworkControllerImpl.java
index 2465c21..73f181b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/NetworkControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/NetworkControllerImpl.java
@@ -74,7 +74,7 @@
 import com.android.systemui.demomode.DemoModeController;
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.log.LogBuffer;
-import com.android.systemui.log.LogLevel;
+import com.android.systemui.log.core.LogLevel;
 import com.android.systemui.log.dagger.StatusBarNetworkControllerLog;
 import com.android.systemui.qs.tiles.dialog.InternetDialogFactory;
 import com.android.systemui.settings.UserTracker;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesDependenciesModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesDependenciesModule.java
index 075b41b..035fa04 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesDependenciesModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesDependenciesModule.java
@@ -41,6 +41,8 @@
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.power.domain.interactor.PowerInteractor;
 import com.android.systemui.settings.DisplayTracker;
+import com.android.systemui.shade.NotificationPanelViewController;
+import com.android.systemui.shade.ShadeSurface;
 import com.android.systemui.shade.carrier.ShadeCarrierGroupController;
 import com.android.systemui.statusbar.ActionClickLogger;
 import com.android.systemui.statusbar.CommandQueue;
@@ -273,6 +275,21 @@
         return ongoingCallController;
     }
 
+    /**
+     * {@link NotificationPanelViewController} implements two interfaces:
+     *  - {@link com.android.systemui.shade.ShadeViewController}, which can be used by any class
+     *    needing access to the shade.
+     *  - {@link ShadeSurface}, which should *only* be used by {@link CentralSurfacesImpl}.
+     *
+     * Since {@link ShadeSurface} should only be accessible by {@link CentralSurfacesImpl}, it's
+     * *only* bound in this CentralSurfaces dependencies module.
+     * The {@link com.android.systemui.shade.ShadeViewController} interface is bound in
+     * {@link com.android.systemui.shade.ShadeModule} so others can access it.
+     */
+    @Binds
+    @SysUISingleton
+    ShadeSurface provideShadeSurface(NotificationPanelViewController impl);
+
     /** */
     @Binds
     ShadeCarrierGroupController.SlotIndexResolver provideSlotIndexResolver(
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/disableflags/data/model/DisableFlagsModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/disableflags/data/model/DisableFlagsModel.kt
index ac05248..2bb4765 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/disableflags/data/model/DisableFlagsModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/disableflags/data/model/DisableFlagsModel.kt
@@ -20,7 +20,7 @@
 import android.app.StatusBarManager.DISABLE_NONE
 import android.app.StatusBarManager.DISABLE_NOTIFICATION_ALERTS
 import com.android.systemui.log.LogBuffer
-import com.android.systemui.log.LogLevel
+import com.android.systemui.log.core.LogLevel
 import com.android.systemui.statusbar.disableflags.DisableFlagsLogger
 
 /**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/events/StatusBarEventsModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/events/StatusBarEventsModule.kt
index 3d6d489..84796f9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/events/StatusBarEventsModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/events/StatusBarEventsModule.kt
@@ -22,6 +22,8 @@
 import com.android.systemui.dump.DumpManager
 import com.android.systemui.flags.FeatureFlags
 import com.android.systemui.flags.Flags
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.LogBufferFactory
 import com.android.systemui.statusbar.window.StatusBarWindowController
 import com.android.systemui.util.concurrency.DelayableExecutor
 import com.android.systemui.util.time.SystemClock
@@ -36,6 +38,13 @@
 
         @Provides
         @SysUISingleton
+        @SystemStatusAnimationSchedulerLog
+        fun provideSystemStatusAnimationSchedulerLogBuffer(factory: LogBufferFactory): LogBuffer {
+            return factory.create("SystemStatusAnimationSchedulerLog", 60)
+        }
+
+        @Provides
+        @SysUISingleton
         fun provideSystemStatusAnimationScheduler(
                 featureFlags: FeatureFlags,
                 coordinator: SystemEventCoordinator,
@@ -44,7 +53,8 @@
                 dumpManager: DumpManager,
                 systemClock: SystemClock,
                 @Application coroutineScope: CoroutineScope,
-                @Main executor: DelayableExecutor
+                @Main executor: DelayableExecutor,
+                logger: SystemStatusAnimationSchedulerLogger
         ): SystemStatusAnimationScheduler {
             return if (featureFlags.isEnabled(Flags.PLUG_IN_STATUS_BAR_CHIP)) {
                 SystemStatusAnimationSchedulerImpl(
@@ -53,7 +63,8 @@
                         statusBarWindowController,
                         dumpManager,
                         systemClock,
-                        coroutineScope
+                        coroutineScope,
+                        logger
                 )
             } else {
                 SystemStatusAnimationSchedulerLegacyImpl(
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemStatusAnimationSchedulerImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemStatusAnimationSchedulerImpl.kt
index 56ea703..6fc715a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemStatusAnimationSchedulerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemStatusAnimationSchedulerImpl.kt
@@ -18,7 +18,6 @@
 
 import android.os.Process
 import android.provider.DeviceConfig
-import android.util.Log
 import androidx.core.animation.Animator
 import androidx.core.animation.AnimatorListenerAdapter
 import androidx.core.animation.AnimatorSet
@@ -69,7 +68,8 @@
     private val statusBarWindowController: StatusBarWindowController,
     dumpManager: DumpManager,
     private val systemClock: SystemClock,
-    @Application private val coroutineScope: CoroutineScope
+    @Application private val coroutineScope: CoroutineScope,
+    private val logger: SystemStatusAnimationSchedulerLogger?
 ) : SystemStatusAnimationScheduler {
 
     companion object {
@@ -121,6 +121,10 @@
                     }
                 }
         }
+
+        coroutineScope.launch {
+            animationState.collect { logger?.logAnimationStateUpdate(it) }
+        }
     }
 
     @SystemAnimationState override fun getAnimationState(): Int = animationState.value
@@ -140,32 +144,17 @@
         ) {
             // a event can only be scheduled if no other event is in progress or it has a higher
             // priority. If a persistent dot is currently displayed, don't schedule the event.
-            if (DEBUG) {
-                Log.d(TAG, "scheduling event $event")
-            }
-
+            logger?.logScheduleEvent(event)
             scheduleEvent(event)
         } else if (currentlyDisplayedEvent?.shouldUpdateFromEvent(event) == true) {
-            if (DEBUG) {
-                Log.d(
-                    TAG,
-                    "updating current event from: $event. animationState=${animationState.value}"
-                )
-            }
+            logger?.logUpdateEvent(event, animationState.value)
             currentlyDisplayedEvent?.updateFromEvent(event)
             if (event.forceVisible) hasPersistentDot = true
         } else if (scheduledEvent.value?.shouldUpdateFromEvent(event) == true) {
-            if (DEBUG) {
-                Log.d(
-                    TAG,
-                    "updating scheduled event from: $event. animationState=${animationState.value}"
-                )
-            }
+            logger?.logUpdateEvent(event, animationState.value)
             scheduledEvent.value?.updateFromEvent(event)
         } else {
-            if (DEBUG) {
-                Log.d(TAG, "ignoring event $event")
-            }
+            logger?.logIgnoreEvent(event)
         }
     }
 
@@ -356,6 +345,7 @@
     }
 
     private fun notifyTransitionToPersistentDot(): Animator? {
+        logger?.logTransitionToPersistentDotCallbackInvoked()
         val anims: List<Animator> =
             listeners.mapNotNull {
                 it.onSystemStatusAnimationTransitionToPersistentDot(
@@ -373,6 +363,7 @@
 
     private fun notifyHidePersistentDot(): Animator? {
         Assert.isMainThread()
+        logger?.logHidePersistentDotCallbackInvoked()
         val anims: List<Animator> = listeners.mapNotNull { it.onHidePersistentDot() }
 
         if (animationState.value == SHOWING_PERSISTENT_DOT) {
@@ -424,5 +415,4 @@
     }
 }
 
-private const val DEBUG = false
 private const val TAG = "SystemStatusAnimationSchedulerImpl"
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemStatusAnimationSchedulerLog.kt b/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemStatusAnimationSchedulerLog.kt
new file mode 100644
index 0000000..4ac94a6
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemStatusAnimationSchedulerLog.kt
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.events
+
+import javax.inject.Qualifier
+
+/** Logs for the SystemStatusAnimationScheduler. */
+@Qualifier
+@MustBeDocumented
+@kotlin.annotation.Retention(AnnotationRetention.RUNTIME)
+annotation class SystemStatusAnimationSchedulerLog
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemStatusAnimationSchedulerLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemStatusAnimationSchedulerLogger.kt
new file mode 100644
index 0000000..22b0b69
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemStatusAnimationSchedulerLogger.kt
@@ -0,0 +1,92 @@
+package com.android.systemui.statusbar.events
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.core.LogLevel
+import javax.inject.Inject
+
+/** Logs for the SystemStatusAnimationScheduler. */
+@SysUISingleton
+class SystemStatusAnimationSchedulerLogger
+@Inject
+constructor(
+    @SystemStatusAnimationSchedulerLog private val logBuffer: LogBuffer,
+) {
+
+    fun logScheduleEvent(event: StatusEvent) {
+        logBuffer.log(
+            TAG,
+            LogLevel.DEBUG,
+            {
+                str1 = event.javaClass.simpleName
+                int1 = event.priority
+                bool1 = event.forceVisible
+                bool2 = event.showAnimation
+            },
+            { "Scheduling event: $str1(forceVisible=$bool1, priority=$int1, showAnimation=$bool2)" }
+        )
+    }
+
+    fun logUpdateEvent(event: StatusEvent, @SystemAnimationState animationState: Int) {
+        logBuffer.log(
+            TAG,
+            LogLevel.DEBUG,
+            {
+                str1 = event.javaClass.simpleName
+                int1 = event.priority
+                bool1 = event.forceVisible
+                bool2 = event.showAnimation
+                int2 = animationState
+            },
+            {
+                "Updating current event from: $str1(forceVisible=$bool1, priority=$int1, " +
+                    "showAnimation=$bool2), animationState=${animationState.name()}"
+            }
+        )
+    }
+
+    fun logIgnoreEvent(event: StatusEvent) {
+        logBuffer.log(
+            TAG,
+            LogLevel.DEBUG,
+            {
+                str1 = event.javaClass.simpleName
+                int1 = event.priority
+                bool1 = event.forceVisible
+                bool2 = event.showAnimation
+            },
+            { "Ignore event: $str1(forceVisible=$bool1, priority=$int1, showAnimation=$bool2)" }
+        )
+    }
+
+    fun logHidePersistentDotCallbackInvoked() {
+        logBuffer.log(TAG, LogLevel.DEBUG, "Hide persistent dot callback invoked")
+    }
+
+    fun logTransitionToPersistentDotCallbackInvoked() {
+        logBuffer.log(TAG, LogLevel.DEBUG, "Transition to persistent dot callback invoked")
+    }
+
+    fun logAnimationStateUpdate(@SystemAnimationState animationState: Int) {
+        logBuffer.log(
+            TAG,
+            LogLevel.DEBUG,
+            { int1 = animationState },
+            { "AnimationState update: ${int1.name()}" }
+        )
+        animationState.name()
+    }
+
+    private fun @receiver:SystemAnimationState Int.name() =
+        when (this) {
+            IDLE -> "IDLE"
+            ANIMATION_QUEUED -> "ANIMATION_QUEUED"
+            ANIMATING_IN -> "ANIMATING_IN"
+            RUNNING_CHIP_ANIM -> "RUNNING_CHIP_ANIM"
+            ANIMATING_OUT -> "ANIMATING_OUT"
+            SHOWING_PERSISTENT_DOT -> "SHOWING_PERSISTENT_DOT"
+            else -> "UNKNOWN_ANIMATION_STATE"
+        }
+}
+
+private const val TAG = "SystemStatusAnimationSchedulerLog"
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/gesture/SwipeUpGestureLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/gesture/SwipeUpGestureLogger.kt
index a67c26c..96725fc 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/gesture/SwipeUpGestureLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/gesture/SwipeUpGestureLogger.kt
@@ -19,7 +19,7 @@
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.log.dagger.SwipeUpLog
 import com.android.systemui.log.LogBuffer
-import com.android.systemui.log.LogLevel
+import com.android.systemui.log.core.LogLevel
 import javax.inject.Inject
 
 /** Log messages for [SwipeUpGestureHandler]. */
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationClickerLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationClickerLogger.kt
index a3a72d9..cea2b59 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationClickerLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationClickerLogger.kt
@@ -18,7 +18,7 @@
 
 import com.android.systemui.log.dagger.NotifInteractionLog
 import com.android.systemui.log.LogBuffer
-import com.android.systemui.log.LogLevel
+import com.android.systemui.log.core.LogLevel
 import com.android.systemui.statusbar.notification.collection.NotificationEntry
 import javax.inject.Inject
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinatorLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinatorLogger.kt
index f7679ed..502e1d9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinatorLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinatorLogger.kt
@@ -14,7 +14,7 @@
 package com.android.systemui.statusbar.notification
 
 import com.android.systemui.log.LogBuffer
-import com.android.systemui.log.LogLevel.DEBUG
+import com.android.systemui.log.core.LogLevel.DEBUG
 import com.android.systemui.log.dagger.NotificationLockscreenLog
 import com.android.systemui.statusbar.StatusBarState
 import javax.inject.Inject
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/RemoteInputControllerLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/RemoteInputControllerLogger.kt
index 487a5f8..7809eaa 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/RemoteInputControllerLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/RemoteInputControllerLogger.kt
@@ -18,7 +18,7 @@
 
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.log.LogBuffer
-import com.android.systemui.log.LogLevel.DEBUG
+import com.android.systemui.log.core.LogLevel.DEBUG
 import com.android.systemui.log.dagger.NotificationRemoteInputLog
 import javax.inject.Inject
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coalescer/GroupCoalescerLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coalescer/GroupCoalescerLogger.kt
index 39d0833..0ab348d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coalescer/GroupCoalescerLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coalescer/GroupCoalescerLogger.kt
@@ -18,7 +18,7 @@
 
 import com.android.systemui.log.dagger.NotificationLog
 import com.android.systemui.log.LogBuffer
-import com.android.systemui.log.LogLevel
+import com.android.systemui.log.core.LogLevel
 import javax.inject.Inject
 
 class GroupCoalescerLogger @Inject constructor(
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/GutsCoordinatorLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/GutsCoordinatorLogger.kt
index 79c63e6..bd1141e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/GutsCoordinatorLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/GutsCoordinatorLogger.kt
@@ -2,7 +2,7 @@
 
 import com.android.systemui.log.dagger.NotificationLog
 import com.android.systemui.log.LogBuffer
-import com.android.systemui.log.LogLevel
+import com.android.systemui.log.core.LogLevel
 import com.android.systemui.statusbar.notification.row.NotificationGuts
 import javax.inject.Inject
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorLogger.kt
index e17ce5c..496fb83 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorLogger.kt
@@ -4,7 +4,7 @@
 
 import com.android.systemui.log.dagger.NotificationHeadsUpLog
 import com.android.systemui.log.LogBuffer
-import com.android.systemui.log.LogLevel
+import com.android.systemui.log.core.LogLevel
 import javax.inject.Inject
 
 private const val TAG = "HeadsUpCoordinator"
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinatorLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinatorLogger.kt
index 1f8ec34..4c33524 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinatorLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinatorLogger.kt
@@ -17,7 +17,7 @@
 package com.android.systemui.statusbar.notification.collection.coordinator
 
 import com.android.systemui.log.LogBuffer
-import com.android.systemui.log.LogLevel
+import com.android.systemui.log.core.LogLevel
 import com.android.systemui.log.dagger.UnseenNotificationLog
 import javax.inject.Inject
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinatorLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinatorLogger.kt
index 6271d38..bf65043 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinatorLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinatorLogger.kt
@@ -18,7 +18,7 @@
 
 import com.android.systemui.log.dagger.NotificationLog
 import com.android.systemui.log.LogBuffer
-import com.android.systemui.log.LogLevel
+import com.android.systemui.log.core.LogLevel
 import com.android.systemui.statusbar.notification.collection.GroupEntry
 import com.android.systemui.statusbar.notification.collection.NotificationEntry
 import com.android.systemui.statusbar.notification.logKey
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ShadeEventCoordinatorLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ShadeEventCoordinatorLogger.kt
index 1f4861a..0d9681f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ShadeEventCoordinatorLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ShadeEventCoordinatorLogger.kt
@@ -18,7 +18,7 @@
 
 import com.android.systemui.log.dagger.NotificationLog
 import com.android.systemui.log.LogBuffer
-import com.android.systemui.log.LogLevel
+import com.android.systemui.log.core.LogLevel
 import javax.inject.Inject
 
 private const val TAG = "ShadeEventCoordinator"
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/ShadeListBuilderLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/ShadeListBuilderLogger.kt
index f13ff68..a8409d0c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/ShadeListBuilderLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/ShadeListBuilderLogger.kt
@@ -18,9 +18,9 @@
 
 import com.android.systemui.log.dagger.NotificationLog
 import com.android.systemui.log.LogBuffer
-import com.android.systemui.log.LogLevel.DEBUG
-import com.android.systemui.log.LogLevel.INFO
-import com.android.systemui.log.LogLevel.WARNING
+import com.android.systemui.log.core.LogLevel.DEBUG
+import com.android.systemui.log.core.LogLevel.INFO
+import com.android.systemui.log.core.LogLevel.WARNING
 import com.android.systemui.statusbar.notification.NotifPipelineFlags
 import com.android.systemui.statusbar.notification.collection.GroupEntry
 import com.android.systemui.statusbar.notification.collection.ListEntry
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifCollectionLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifCollectionLogger.kt
index 73227ab..014ac54 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifCollectionLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifCollectionLogger.kt
@@ -22,11 +22,11 @@
 import android.service.notification.StatusBarNotification
 import com.android.systemui.log.dagger.NotificationLog
 import com.android.systemui.log.LogBuffer
-import com.android.systemui.log.LogLevel.DEBUG
-import com.android.systemui.log.LogLevel.ERROR
-import com.android.systemui.log.LogLevel.INFO
-import com.android.systemui.log.LogLevel.WARNING
-import com.android.systemui.log.LogLevel.WTF
+import com.android.systemui.log.core.LogLevel.DEBUG
+import com.android.systemui.log.core.LogLevel.ERROR
+import com.android.systemui.log.core.LogLevel.INFO
+import com.android.systemui.log.core.LogLevel.WARNING
+import com.android.systemui.log.core.LogLevel.WTF
 import com.android.systemui.statusbar.notification.collection.NotifCollection
 import com.android.systemui.statusbar.notification.collection.NotifCollection.CancellationReason
 import com.android.systemui.statusbar.notification.collection.NotifCollection.FutureDismissal
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NodeSpecBuilderLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NodeSpecBuilderLogger.kt
index 07fd349..e61f9bd 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NodeSpecBuilderLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NodeSpecBuilderLogger.kt
@@ -18,7 +18,7 @@
 
 import com.android.systemui.log.dagger.NotificationLog
 import com.android.systemui.log.LogBuffer
-import com.android.systemui.log.LogLevel
+import com.android.systemui.log.core.LogLevel
 import com.android.systemui.statusbar.notification.NotifPipelineFlags
 import com.android.systemui.statusbar.notification.collection.listbuilder.NotifSection
 import com.android.systemui.util.Compile
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDifferLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDifferLogger.kt
index a880b71..082f308 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDifferLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDifferLogger.kt
@@ -18,7 +18,7 @@
 
 import com.android.systemui.log.dagger.NotificationLog
 import com.android.systemui.log.LogBuffer
-import com.android.systemui.log.LogLevel
+import com.android.systemui.log.core.LogLevel
 import java.lang.RuntimeException
 import javax.inject.Inject
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/HeadsUpViewBinderLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/HeadsUpViewBinderLogger.kt
index 0b31265..c6d2861 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/HeadsUpViewBinderLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/HeadsUpViewBinderLogger.kt
@@ -2,7 +2,7 @@
 
 import com.android.systemui.log.dagger.NotificationHeadsUpLog
 import com.android.systemui.log.LogBuffer
-import com.android.systemui.log.LogLevel.INFO
+import com.android.systemui.log.core.LogLevel.INFO
 import com.android.systemui.statusbar.notification.collection.NotificationEntry
 import com.android.systemui.statusbar.notification.logKey
 import javax.inject.Inject
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptLogger.kt
index 5bac2a9..4a823a4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptLogger.kt
@@ -20,9 +20,9 @@
 
 import com.android.systemui.log.dagger.NotificationInterruptLog
 import com.android.systemui.log.LogBuffer
-import com.android.systemui.log.LogLevel.DEBUG
-import com.android.systemui.log.LogLevel.INFO
-import com.android.systemui.log.LogLevel.WARNING
+import com.android.systemui.log.core.LogLevel.DEBUG
+import com.android.systemui.log.core.LogLevel.INFO
+import com.android.systemui.log.core.LogLevel.WARNING
 import com.android.systemui.statusbar.notification.collection.NotificationEntry
 import com.android.systemui.statusbar.notification.logKey
 import com.android.systemui.util.Compile
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationRoundnessLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationRoundnessLogger.kt
index fe03b2a..0e1f66f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationRoundnessLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationRoundnessLogger.kt
@@ -17,7 +17,7 @@
 package com.android.systemui.statusbar.notification.logging
 
 import com.android.systemui.log.LogBuffer
-import com.android.systemui.log.LogLevel.INFO
+import com.android.systemui.log.core.LogLevel.INFO
 import com.android.systemui.log.dagger.NotificationRenderLog
 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/row/NotifBindPipelineLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifBindPipelineLogger.kt
index 45be0b1..3856700 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifBindPipelineLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifBindPipelineLogger.kt
@@ -18,7 +18,7 @@
 
 import com.android.systemui.log.dagger.NotificationLog
 import com.android.systemui.log.LogBuffer
-import com.android.systemui.log.LogLevel.INFO
+import com.android.systemui.log.core.LogLevel.INFO
 import com.android.systemui.statusbar.notification.collection.NotificationEntry
 import com.android.systemui.statusbar.notification.logKey
 import javax.inject.Inject
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowLogger.kt
index 89338f9..4f5a04f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowLogger.kt
@@ -19,7 +19,7 @@
 
 import android.view.ViewGroup
 import com.android.systemui.log.LogBuffer
-import com.android.systemui.log.LogLevel
+import com.android.systemui.log.core.LogLevel
 import com.android.systemui.log.dagger.NotificationLog
 import com.android.systemui.log.dagger.NotificationRenderLog
 import com.android.systemui.statusbar.notification.collection.NotificationEntry
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowContentBindStageLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowContentBindStageLogger.kt
index 684a276..02627fd 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowContentBindStageLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowContentBindStageLogger.kt
@@ -18,7 +18,7 @@
 
 import com.android.systemui.log.dagger.NotificationLog
 import com.android.systemui.log.LogBuffer
-import com.android.systemui.log.LogLevel.INFO
+import com.android.systemui.log.core.LogLevel.INFO
 import com.android.systemui.statusbar.notification.collection.NotificationEntry
 import com.android.systemui.statusbar.notification.logKey
 import javax.inject.Inject
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainerLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainerLogger.kt
index 6be1ef8..4986b63 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainerLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainerLogger.kt
@@ -17,7 +17,7 @@
 package com.android.systemui.statusbar.notification.stack
 
 import com.android.systemui.log.LogBuffer
-import com.android.systemui.log.LogLevel
+import com.android.systemui.log.core.LogLevel
 import com.android.systemui.log.dagger.NotificationRenderLog
 import com.android.systemui.statusbar.notification.collection.NotificationEntry
 import com.android.systemui.statusbar.notification.logKey
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsLogger.kt
index f953187..2da5582 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsLogger.kt
@@ -19,7 +19,7 @@
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.log.dagger.NotificationSectionLog
 import com.android.systemui.log.LogBuffer
-import com.android.systemui.log.LogLevel
+import com.android.systemui.log.core.LogLevel
 import javax.inject.Inject
 
 private const val TAG = "NotifSections"
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLogger.kt
index 9c1bd17..2c38b8d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLogger.kt
@@ -1,11 +1,11 @@
 package com.android.systemui.statusbar.notification.stack
 
 import android.view.ViewGroup
-import com.android.systemui.log.dagger.NotificationHeadsUpLog
 import com.android.systemui.log.LogBuffer
-import com.android.systemui.log.LogLevel.DEBUG
-import com.android.systemui.log.LogLevel.INFO
-import com.android.systemui.log.LogLevel.ERROR
+import com.android.systemui.log.core.LogLevel.DEBUG
+import com.android.systemui.log.core.LogLevel.INFO
+import com.android.systemui.log.core.LogLevel.ERROR
+import com.android.systemui.log.dagger.NotificationHeadsUpLog
 import com.android.systemui.log.dagger.NotificationRenderLog
 import com.android.systemui.statusbar.notification.collection.NotificationEntry
 import com.android.systemui.statusbar.notification.logKey
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateLogger.kt
index c7f80f3..0b2c486 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateLogger.kt
@@ -1,8 +1,8 @@
 package com.android.systemui.statusbar.notification.stack
 
-import com.android.systemui.log.dagger.NotificationHeadsUpLog
 import com.android.systemui.log.LogBuffer
-import com.android.systemui.log.LogLevel
+import com.android.systemui.log.core.LogLevel
+import com.android.systemui.log.dagger.NotificationHeadsUpLog
 import com.android.systemui.log.dagger.NotificationRenderLog
 import com.android.systemui.statusbar.notification.logKey
 import javax.inject.Inject
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/AutoTileManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/AutoTileManager.java
index f6d53b3..5eafa9e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/AutoTileManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/AutoTileManager.java
@@ -20,6 +20,7 @@
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.res.Resources;
+import android.database.ContentObserver;
 import android.hardware.display.ColorDisplayManager;
 import android.hardware.display.NightDisplayListener;
 import android.os.Handler;
@@ -28,6 +29,7 @@
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.systemui.R;
+import com.android.systemui.dagger.NightDisplayListenerModule;
 import com.android.systemui.dagger.qualifiers.Background;
 import com.android.systemui.plugins.qs.QSTile;
 import com.android.systemui.qs.AutoAddTracker;
@@ -82,7 +84,8 @@
     private final HotspotController mHotspotController;
     private final DataSaverController mDataSaverController;
     private final ManagedProfileController mManagedProfileController;
-    private final NightDisplayListener mNightDisplayListener;
+    private final NightDisplayListenerModule.Builder mNightDisplayListenerBuilder;
+    private NightDisplayListener mNightDisplayListener;
     private final CastController mCastController;
     private final DeviceControlsController mDeviceControlsController;
     private final WalletController mWalletController;
@@ -98,7 +101,7 @@
             HotspotController hotspotController,
             DataSaverController dataSaverController,
             ManagedProfileController managedProfileController,
-            NightDisplayListener nightDisplayListener,
+            NightDisplayListenerModule.Builder nightDisplayListenerBuilder,
             CastController castController,
             ReduceBrightColorsController reduceBrightColorsController,
             DeviceControlsController deviceControlsController,
@@ -114,7 +117,7 @@
         mHotspotController = hotspotController;
         mDataSaverController = dataSaverController;
         mManagedProfileController = managedProfileController;
-        mNightDisplayListener = nightDisplayListener;
+        mNightDisplayListenerBuilder = nightDisplayListenerBuilder;
         mCastController = castController;
         mReduceBrightColorsController = reduceBrightColorsController;
         mIsReduceBrightColorsAvailable = isReduceBrightColorsAvailable;
@@ -157,6 +160,10 @@
             mDataSaverController.addCallback(mDataSaverListener);
         }
         mManagedProfileController.addCallback(mProfileCallback);
+
+        mNightDisplayListener = mNightDisplayListenerBuilder
+                .setUser(mCurrentUser.getIdentifier())
+                .build();
         if (!mAutoTracker.isAdded(NIGHT)
                 && ColorDisplayManager.isNightDisplayAvailable(mContext)) {
             mNightDisplayListener.setCallback(mNightDisplayCallback);
@@ -193,7 +200,8 @@
         mHotspotController.removeCallback(mHotspotCallback);
         mDataSaverController.removeCallback(mDataSaverListener);
         mManagedProfileController.removeCallback(mProfileCallback);
-        if (ColorDisplayManager.isNightDisplayAvailable(mContext)) {
+        if (ColorDisplayManager.isNightDisplayAvailable(mContext)
+                && mNightDisplayListener != null) {
             mNightDisplayListener.setCallback(null);
         }
         if (mIsReduceBrightColorsAvailable) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java
index 1754f0c..478baf2f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java
@@ -40,6 +40,7 @@
 import com.android.keyguard.AuthKeyguardMessageArea;
 import com.android.systemui.Dumpable;
 import com.android.systemui.animation.ActivityLaunchAnimator;
+import com.android.systemui.display.data.repository.DisplayMetricsRepository;
 import com.android.systemui.navigationbar.NavigationBarView;
 import com.android.systemui.plugins.ActivityStarter.OnDismissAction;
 import com.android.systemui.qs.QSPanelController;
@@ -248,8 +249,12 @@
     @Override
     void dump(PrintWriter pwOriginal, String[] args);
 
+    /** @deprecated Use {@link DisplayMetricsRepository} instead. */
+    @Deprecated
     float getDisplayWidth();
 
+    /** @deprecated Use {@link DisplayMetricsRepository} instead. */
+    @Deprecated
     float getDisplayHeight();
 
     void readyForKeyguardDone();
@@ -384,6 +389,9 @@
     void setLaunchEmergencyActionOnFinishedWaking(boolean launch);
 
     QSPanelController getQSPanelController();
+
+    /** @deprecated Use {@link DisplayMetricsRepository} instead. */
+    @Deprecated
     float getDisplayDensity();
 
     void extendDozePulse();
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 41f0c11..39b13d9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
@@ -182,7 +182,6 @@
 import com.android.systemui.settings.UserTracker;
 import com.android.systemui.settings.brightness.BrightnessSliderController;
 import com.android.systemui.shade.CameraLauncher;
-import com.android.systemui.shade.NotificationPanelViewController;
 import com.android.systemui.shade.NotificationShadeWindowView;
 import com.android.systemui.shade.NotificationShadeWindowViewController;
 import com.android.systemui.shade.QuickSettingsController;
@@ -477,14 +476,12 @@
     private final Lazy<LightRevealScrimViewModel> mLightRevealScrimViewModelLazy;
 
     /** Controller for the Shade. */
-    @VisibleForTesting
-    ShadeSurface mShadeSurface;
+    private final ShadeSurface mShadeSurface;
     private final ShadeLogger mShadeLogger;
 
     // settings
     private QSPanelController mQSPanelController;
-    @VisibleForTesting
-    QuickSettingsController mQsController;
+    private final QuickSettingsController mQsController;
 
     KeyguardIndicationController mKeyguardIndicationController;
 
@@ -704,9 +701,11 @@
             MetricsLogger metricsLogger,
             ShadeLogger shadeLogger,
             @UiBackground Executor uiBgExecutor,
+            ShadeSurface shadeSurface,
             NotificationMediaManager notificationMediaManager,
             NotificationLockscreenUserManager lockScreenUserManager,
             NotificationRemoteInputManager remoteInputManager,
+            QuickSettingsController quickSettingsController,
             UserSwitcherController userSwitcherController,
             BatteryController batteryController,
             SysuiColorExtractor colorExtractor,
@@ -805,9 +804,11 @@
         mMetricsLogger = metricsLogger;
         mShadeLogger = shadeLogger;
         mUiBgExecutor = uiBgExecutor;
+        mShadeSurface = shadeSurface;
         mMediaManager = notificationMediaManager;
         mLockscreenUserManager = lockScreenUserManager;
         mRemoteInputManager = remoteInputManager;
+        mQsController = quickSettingsController;
         mUserSwitcherController = userSwitcherController;
         mBatteryController = batteryController;
         mColorExtractor = colorExtractor;
@@ -1611,13 +1612,9 @@
         //  (Right now, there's a circular dependency.)
         mNotificationShadeWindowController.setWindowRootView(windowRootView);
         mNotificationShadeWindowViewController.setupExpandedStatusBar();
-        NotificationPanelViewController npvc =
-                mCentralSurfacesComponent.getNotificationPanelViewController();
-        mShadeSurface = npvc;
-        mShadeController.setNotificationPanelViewController(npvc);
+        mShadeController.setShadeViewController(mShadeSurface);
         mShadeController.setNotificationShadeWindowViewController(
                 mNotificationShadeWindowViewController);
-        mQsController = mCentralSurfacesComponent.getQuickSettingsController();
         mBackActionInteractor.setup(mQsController, mShadeSurface);
         mPresenter = mCentralSurfacesComponent.getNotificationPresenter();
         mNotificationActivityStarter = mCentralSurfacesComponent.getNotificationActivityStarter();
@@ -1813,7 +1810,7 @@
 
     @Override
     public void onStatusBarTrackpadEvent(MotionEvent event) {
-        mCentralSurfacesComponent.getNotificationPanelViewController().handleExternalTouch(event);
+        mShadeSurface.handleExternalTouch(event);
     }
 
     private void onExpandedInvisible() {
@@ -2084,16 +2081,19 @@
     }
 
     @Override
+    @Deprecated
     public float getDisplayDensity() {
         return mDisplayMetrics.density;
     }
 
     @Override
+    @Deprecated
     public float getDisplayWidth() {
         return mDisplayMetrics.widthPixels;
     }
 
     @Override
+    @Deprecated
     public float getDisplayHeight() {
         return mDisplayMetrics.heightPixels;
     }
@@ -2180,7 +2180,6 @@
         if (mLockscreenWallpaper != null && !mWallpaperManager.isLockscreenLiveWallpaperEnabled()) {
             mLockscreenWallpaper.setCurrentUser(newUserId);
         }
-        mScrimController.setCurrentUser(newUserId);
         if (mWallpaperSupported) {
             mWallpaperChangedReceiver.onReceive(mContext, null);
         }
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 4d716c2..680f19a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java
@@ -45,7 +45,7 @@
 import com.android.systemui.R;
 import com.android.systemui.battery.BatteryMeterViewController;
 import com.android.systemui.dagger.qualifiers.Main;
-import com.android.systemui.log.LogLevel;
+import com.android.systemui.log.core.LogLevel;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.shade.ShadeViewStateProvider;
 import com.android.systemui.statusbar.CommandQueue;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LSShadeTransitionLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LSShadeTransitionLogger.kt
index 5c357d7..686efb7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LSShadeTransitionLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LSShadeTransitionLogger.kt
@@ -21,7 +21,7 @@
 import com.android.internal.logging.nano.MetricsProto.MetricsEvent
 import com.android.systemui.log.dagger.LSShadeTransitionLog
 import com.android.systemui.log.LogBuffer
-import com.android.systemui.log.LogLevel
+import com.android.systemui.log.core.LogLevel
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
 import com.android.systemui.statusbar.notification.row.ExpandableView
 import javax.inject.Inject
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java
index e6b76ad..3b5aaea 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java
@@ -48,6 +48,7 @@
 import com.android.systemui.dagger.qualifiers.DisplayId;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.dagger.qualifiers.UiBackground;
+import com.android.systemui.display.domain.interactor.ConnectedDisplayInteractor;
 import com.android.systemui.privacy.PrivacyItem;
 import com.android.systemui.privacy.PrivacyItemController;
 import com.android.systemui.privacy.PrivacyType;
@@ -74,6 +75,7 @@
 import com.android.systemui.statusbar.policy.UserInfoController;
 import com.android.systemui.statusbar.policy.ZenModeController;
 import com.android.systemui.util.RingerModeTracker;
+import com.android.systemui.util.kotlin.JavaAdapter;
 import com.android.systemui.util.time.DateFormatUtil;
 
 import java.io.PrintWriter;
@@ -121,9 +123,12 @@
     private final String mSlotCamera;
     private final String mSlotSensorsOff;
     private final String mSlotScreenRecord;
+    private final String mSlotConnectedDisplay;
     private final int mDisplayId;
     private final SharedPreferences mSharedPreferences;
     private final DateFormatUtil mDateFormatUtil;
+    private final JavaAdapter mJavaAdapter;
+    private final ConnectedDisplayInteractor mConnectedDisplayInteractor;
     private final TelecomManager mTelecomManager;
 
     private final Handler mHandler;
@@ -182,9 +187,13 @@
             @Main SharedPreferences sharedPreferences, DateFormatUtil dateFormatUtil,
             RingerModeTracker ringerModeTracker,
             PrivacyItemController privacyItemController,
-            PrivacyLogger privacyLogger) {
+            PrivacyLogger privacyLogger,
+            ConnectedDisplayInteractor connectedDisplayInteractor,
+            JavaAdapter javaAdapter
+    ) {
         mIconController = iconController;
         mCommandQueue = commandQueue;
+        mConnectedDisplayInteractor = connectedDisplayInteractor;
         mBroadcastDispatcher = broadcastDispatcher;
         mHandler = new Handler(looper);
         mResources = resources;
@@ -211,8 +220,11 @@
         mTelecomManager = telecomManager;
         mRingerModeTracker = ringerModeTracker;
         mPrivacyLogger = privacyLogger;
+        mJavaAdapter = javaAdapter;
 
         mSlotCast = resources.getString(com.android.internal.R.string.status_bar_cast);
+        mSlotConnectedDisplay = resources.getString(
+                com.android.internal.R.string.status_bar_connected_display);
         mSlotHotspot = resources.getString(com.android.internal.R.string.status_bar_hotspot);
         mSlotBluetooth = resources.getString(com.android.internal.R.string.status_bar_bluetooth);
         mSlotTty = resources.getString(com.android.internal.R.string.status_bar_tty);
@@ -285,6 +297,10 @@
         mIconController.setIcon(mSlotCast, R.drawable.stat_sys_cast, null);
         mIconController.setIconVisibility(mSlotCast, false);
 
+        // connected display
+        mIconController.setIcon(mSlotConnectedDisplay, R.drawable.stat_sys_connected_display, null);
+        mIconController.setIconVisibility(mSlotConnectedDisplay, false);
+
         // hotspot
         mIconController.setIcon(mSlotHotspot, R.drawable.stat_sys_hotspot,
                 mResources.getString(R.string.accessibility_status_bar_hotspot));
@@ -342,6 +358,8 @@
         mSensorPrivacyController.addCallback(mSensorPrivacyListener);
         mLocationController.addCallback(this);
         mRecordingController.addCallback(this);
+        mJavaAdapter.alwaysCollectFlow(mConnectedDisplayInteractor.getConnectedDisplayState(),
+                this::onConnectedDisplayAvailabilityChanged);
 
         mCommandQueue.addCallback(this);
     }
@@ -800,4 +818,14 @@
         if (DEBUG) Log.d(TAG, "screenrecord: hiding icon");
         mHandler.post(() -> mIconController.setIconVisibility(mSlotScreenRecord, false));
     }
+
+    private void onConnectedDisplayAvailabilityChanged(ConnectedDisplayInteractor.State state) {
+        boolean visible = state != ConnectedDisplayInteractor.State.DISCONNECTED;
+
+        if (DEBUG) {
+            Log.d(TAG, "connected_display: " + (visible ? "showing" : "hiding") + " icon");
+        }
+
+        mIconController.setIconVisibility(mSlotConnectedDisplay, visible);
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
index 47c4023..c16e13c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
@@ -1476,10 +1476,6 @@
         }
     }
 
-    public void setCurrentUser(int currentUser) {
-        // Don't care in the base class.
-    }
-
     private void updateThemeColors() {
         if (mScrimBehind == null) return;
         int background = Utils.getColorAttr(mScrimBehind.getContext(),
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
index e63875b..cb2a78d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
@@ -703,7 +703,7 @@
 
     @Override
     public void reset(boolean hideBouncerWhenShowing) {
-        if (mKeyguardStateController.isShowing()) {
+        if (mKeyguardStateController.isShowing() && !bouncerIsAnimatingAway()) {
             final boolean isOccluded = mKeyguardStateController.isOccluded();
             // Hide quick settings.
             mShadeViewController.resetViews(/* animate= */ !isOccluded);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterLogger.kt
index 12f023b..d07378e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterLogger.kt
@@ -19,10 +19,10 @@
 import android.app.PendingIntent
 import com.android.systemui.log.dagger.NotifInteractionLog
 import com.android.systemui.log.LogBuffer
-import com.android.systemui.log.LogLevel.DEBUG
-import com.android.systemui.log.LogLevel.ERROR
-import com.android.systemui.log.LogLevel.INFO
-import com.android.systemui.log.LogLevel.WARNING
+import com.android.systemui.log.core.LogLevel.DEBUG
+import com.android.systemui.log.core.LogLevel.ERROR
+import com.android.systemui.log.core.LogLevel.INFO
+import com.android.systemui.log.core.LogLevel.WARNING
 import com.android.systemui.statusbar.notification.collection.NotificationEntry
 import com.android.systemui.statusbar.notification.logKey
 import javax.inject.Inject
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusIconContainer.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusIconContainer.java
index c332280..604b1f5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusIconContainer.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusIconContainer.java
@@ -64,6 +64,7 @@
     private boolean mNeedsUnderflow;
     // Individual StatusBarIconViews draw their etc dots centered in this width
     private int mIconDotFrameWidth;
+    private boolean mQsExpansionTransitioning;
     private boolean mShouldRestrictIcons = true;
     // Used to count which states want to be visible during layout
     private ArrayList<StatusIconState> mLayoutStates = new ArrayList<>();
@@ -87,6 +88,10 @@
         super.onFinishInflate();
     }
 
+    public void setQsExpansionTransitioning(boolean expansionTransitioning) {
+        mQsExpansionTransitioning = expansionTransitioning;
+    }
+
     public void setShouldRestrictIcons(boolean should) {
         mShouldRestrictIcons = should;
     }
@@ -386,6 +391,7 @@
             StatusIconState vs = getViewStateFromChild(child);
             if (vs != null) {
                 vs.applyToView(child);
+                vs.qsExpansionTransitioning = mQsExpansionTransitioning;
             }
         }
     }
@@ -420,6 +426,7 @@
         /// StatusBarIconView.STATE_*
         public int visibleState = STATE_ICON;
         public boolean justAdded = true;
+        public boolean qsExpansionTransitioning = false;
 
         // How far we are from the end of the view actually is the most relevant for animation
         float distanceToViewEnd = -1;
@@ -462,12 +469,13 @@
             }
 
             icon.setVisibleState(visibleState, animateVisibility);
-            if (animationProperties != null) {
+            if (animationProperties != null && !qsExpansionTransitioning) {
                 animateTo(view, animationProperties);
             } else {
                 super.applyToView(view);
             }
 
+            qsExpansionTransitioning = false;
             justAdded = false;
             distanceToViewEnd = currentDistanceToEnd;
 
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 c618be8..4ae460a 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
@@ -21,10 +21,8 @@
 import static java.lang.annotation.RetentionPolicy.RUNTIME;
 
 import com.android.systemui.scene.ui.view.WindowRootView;
-import com.android.systemui.shade.NotificationPanelViewController;
 import com.android.systemui.shade.NotificationShadeWindowView;
 import com.android.systemui.shade.NotificationShadeWindowViewController;
-import com.android.systemui.shade.QuickSettingsController;
 import com.android.systemui.shade.ShadeHeaderController;
 import com.android.systemui.statusbar.NotificationPresenter;
 import com.android.systemui.statusbar.notification.NotificationActivityStarter;
@@ -89,14 +87,6 @@
     NotificationShadeWindowViewController getNotificationShadeWindowViewController();
 
     /**
-     * Creates a NotificationPanelViewController.
-     */
-    NotificationPanelViewController getNotificationPanelViewController();
-
-    /** Creates a QuickSettingsController. */
-    QuickSettingsController getQuickSettingsController();
-
-    /**
      * Creates a StatusBarHeadsUpChangeListener.
      */
     StatusBarHeadsUpChangeListener getStatusBarHeadsUpChangeListener();
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 6b0746f..77381dd 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
@@ -16,22 +16,16 @@
 
 package com.android.systemui.statusbar.phone.dagger;
 
-import android.view.LayoutInflater;
-
 import com.android.keyguard.KeyguardUpdateMonitor;
-import com.android.systemui.R;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.flags.FeatureFlags;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
-import com.android.systemui.shade.NotificationPanelView;
-import com.android.systemui.shade.NotificationPanelViewController;
 import com.android.systemui.shade.ShadeExpansionStateManager;
 import com.android.systemui.shade.ShadeViewController;
 import com.android.systemui.statusbar.CommandQueue;
 import com.android.systemui.statusbar.OperatorNameViewController;
 import com.android.systemui.statusbar.events.SystemStatusAnimationScheduler;
-import com.android.systemui.statusbar.phone.KeyguardBottomAreaView;
 import com.android.systemui.statusbar.phone.NotificationIconAreaController;
 import com.android.systemui.statusbar.phone.StatusBarBoundsProvider;
 import com.android.systemui.statusbar.phone.StatusBarHideIconsForBouncerManager;
@@ -70,12 +64,6 @@
 
     public static final String STATUS_BAR_FRAGMENT = "status_bar_fragment";
 
-    /** */
-    @Binds
-    @CentralSurfacesComponent.CentralSurfacesScope
-    abstract ShadeViewController bindsShadeViewController(
-            NotificationPanelViewController notificationPanelViewController);
-
     @Binds
     @IntoSet
     abstract StatusBarBoundsProvider.BoundsChangeListener sysBarAttrsListenerAsBoundsListener(
@@ -145,17 +133,4 @@
                 statusBarWindowStateController,
                 keyguardUpdateMonitor);
     }
-
-    /**
-     * Constructs a new, unattached {@link KeyguardBottomAreaView}.
-     *
-     * Note that this is explicitly _not_ a singleton, as we want to be able to reinflate it
-     */
-    @Provides
-    public static KeyguardBottomAreaView providesKeyguardBottomAreaView(
-            NotificationPanelView npv, LayoutInflater layoutInflater) {
-        return (KeyguardBottomAreaView) layoutInflater.inflate(R
-                .layout.keyguard_bottom_area, npv, false);
-    }
-
 }
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 fcae23b..0d58079 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
@@ -14,9 +14,6 @@
 
 package com.android.systemui.statusbar.phone.fragment;
 
-import static com.android.systemui.statusbar.events.SystemStatusAnimationSchedulerKt.IDLE;
-import static com.android.systemui.statusbar.events.SystemStatusAnimationSchedulerKt.SHOWING_PERSISTENT_DOT;
-
 import android.annotation.Nullable;
 import android.annotation.SuppressLint;
 import android.app.Fragment;
@@ -39,6 +36,7 @@
 import androidx.core.animation.Animator;
 
 import com.android.app.animation.Interpolators;
+import com.android.app.animation.InterpolatorsAndroidX;
 import com.android.keyguard.KeyguardUpdateMonitor;
 import com.android.systemui.Dumpable;
 import com.android.systemui.R;
@@ -77,6 +75,8 @@
 import com.android.systemui.util.CarrierConfigTracker.DefaultDataSubscriptionChangedListener;
 import com.android.systemui.util.settings.SecureSettings;
 
+import kotlin.Unit;
+
 import java.io.PrintWriter;
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -99,12 +99,16 @@
     private static final String EXTRA_PANEL_STATE = "panel_state";
     public static final String STATUS_BAR_ICON_MANAGER_TAG = "status_bar_icon_manager";
     public static final int FADE_IN_DURATION = 320;
+    public static final int FADE_OUT_DURATION = 160;
     public static final int FADE_IN_DELAY = 50;
+    private static final int SOURCE_SYSTEM_EVENT_ANIMATOR = 1;
+    private static final int SOURCE_OTHER = 2;
     private StatusBarFragmentComponent mStatusBarFragmentComponent;
     private PhoneStatusBarView mStatusBar;
     private final StatusBarStateController mStatusBarStateController;
     private final KeyguardStateController mKeyguardStateController;
     private final ShadeViewController mShadeViewController;
+    private MultiSourceMinAlphaController mEndSideAlphaController;
     private LinearLayout mEndSideContent;
     private View mClockView;
     private View mOngoingCallChip;
@@ -149,7 +153,7 @@
         }
     };
     private OperatorNameViewController mOperatorNameViewController;
-    private StatusBarSystemEventAnimator mSystemEventAnimator;
+    private StatusBarSystemEventDefaultAnimator mSystemEventAnimator;
 
     private final CarrierConfigChangedListener mCarrierConfigCallback =
             new CarrierConfigChangedListener() {
@@ -297,14 +301,14 @@
         updateBlockedIcons();
         mStatusBarIconController.addIconGroup(mDarkIconManager);
         mEndSideContent = mStatusBar.findViewById(R.id.status_bar_end_side_content);
+        mEndSideAlphaController = new MultiSourceMinAlphaController(mEndSideContent);
         mClockView = mStatusBar.findViewById(R.id.clock);
         mOngoingCallChip = mStatusBar.findViewById(R.id.ongoing_call_chip);
         showEndSideContent(false);
         showClock(false);
         initOperatorName();
         initNotificationIconArea();
-        mSystemEventAnimator =
-                new StatusBarSystemEventAnimator(mEndSideContent, getResources());
+        mSystemEventAnimator = getSystemEventAnimator();
         mCarrierConfigTracker.addCallback(mCarrierConfigCallback);
         mCarrierConfigTracker.addDefaultDataSubscriptionChangedListener(mDefaultDataListener);
 
@@ -593,18 +597,27 @@
     }
 
     private void hideEndSideContent(boolean animate) {
-        animateHide(mEndSideContent, animate);
+        if (!animate) {
+            mEndSideAlphaController.setAlpha(/*alpha*/ 0f, SOURCE_OTHER);
+        } else {
+            mEndSideAlphaController.animateToAlpha(/*alpha*/ 0f, SOURCE_OTHER, FADE_OUT_DURATION,
+                    InterpolatorsAndroidX.ALPHA_OUT, /*startDelay*/ 0);
+        }
     }
 
     private void showEndSideContent(boolean animate) {
-        // Only show the system icon area if we are not currently animating
-        int state = mAnimationScheduler.getAnimationState();
-        if (state == IDLE || state == SHOWING_PERSISTENT_DOT) {
-            animateShow(mEndSideContent, animate);
+        if (!animate) {
+            mEndSideAlphaController.setAlpha(1f, SOURCE_OTHER);
+            return;
+        }
+        if (mKeyguardStateController.isKeyguardFadingAway()) {
+            mEndSideAlphaController.animateToAlpha(/*alpha*/ 1f, SOURCE_OTHER,
+                    mKeyguardStateController.getKeyguardFadingAwayDuration(),
+                    InterpolatorsAndroidX.LINEAR_OUT_SLOW_IN,
+                    mKeyguardStateController.getKeyguardFadingAwayDelay());
         } else {
-            // We are in the middle of a system status event animation, which will animate the
-            // alpha (but not the visibility). Allow the view to become visible again
-            mEndSideContent.setVisibility(View.VISIBLE);
+            mEndSideAlphaController.animateToAlpha(/*alpha*/ 1f, SOURCE_OTHER, FADE_IN_DURATION,
+                    InterpolatorsAndroidX.ALPHA_IN, FADE_IN_DELAY);
         }
     }
 
@@ -671,7 +684,7 @@
 
         v.animate()
                 .alpha(0f)
-                .setDuration(160)
+                .setDuration(FADE_OUT_DURATION)
                 .setStartDelay(0)
                 .setInterpolator(Interpolators.ALPHA_OUT)
                 .withEndAction(() -> v.setVisibility(state));
@@ -754,6 +767,16 @@
         return mSystemEventAnimator.onSystemEventAnimationFinish(hasPersistentDot);
     }
 
+    private StatusBarSystemEventDefaultAnimator getSystemEventAnimator() {
+        return new StatusBarSystemEventDefaultAnimator(getResources(), (alpha) -> {
+            mEndSideAlphaController.setAlpha(alpha, SOURCE_SYSTEM_EVENT_ANIMATOR);
+            return Unit.INSTANCE;
+        }, (translationX) -> {
+            mEndSideContent.setTranslationX(translationX);
+            return Unit.INSTANCE;
+        }, /*isAnimationRunning*/ false);
+    }
+
     private void updateStatusBarLocation(int left, int right) {
         int leftMargin = left - mStatusBar.getLeft();
         int rightMargin = mStatusBar.getRight() - right;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentLogger.kt
index f4ab408..7cdb9c0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentLogger.kt
@@ -18,7 +18,7 @@
 
 import com.android.systemui.log.dagger.CollapsedSbFragmentLog
 import com.android.systemui.log.LogBuffer
-import com.android.systemui.log.LogLevel
+import com.android.systemui.log.core.LogLevel
 import com.android.systemui.statusbar.disableflags.DisableFlagsLogger
 import javax.inject.Inject
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/MultiSourceMinAlphaController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/MultiSourceMinAlphaController.kt
new file mode 100644
index 0000000..c8836e4
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/MultiSourceMinAlphaController.kt
@@ -0,0 +1,82 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.phone.fragment
+
+import android.view.View
+import androidx.core.animation.Interpolator
+import androidx.core.animation.ValueAnimator
+import com.android.app.animation.InterpolatorsAndroidX
+
+/**
+ * A controller that keeps track of multiple sources applying alpha value changes to a view. It will
+ * always apply the minimum alpha value of all sources.
+ */
+internal class MultiSourceMinAlphaController
+@JvmOverloads
+constructor(private val view: View, private val initialAlpha: Float = 1f) {
+
+    private val alphas = mutableMapOf<Int, Float>()
+    private val animators = mutableMapOf<Int, ValueAnimator>()
+
+    /**
+     * Sets the alpha of the provided source and applies it to the view (if no other source has set
+     * a lower alpha currently). If an animator of the same source is still running (i.e.
+     * [animateToAlpha] was called before), that animator is cancelled.
+     */
+    fun setAlpha(alpha: Float, sourceId: Int) {
+        animators[sourceId]?.cancel()
+        updateAlpha(alpha, sourceId)
+    }
+
+    /** Animates to the alpha of the provided source. */
+    fun animateToAlpha(
+        alpha: Float,
+        sourceId: Int,
+        duration: Long,
+        interpolator: Interpolator = InterpolatorsAndroidX.ALPHA_IN,
+        startDelay: Long = 0
+    ) {
+        animators[sourceId]?.cancel()
+        val animator = ValueAnimator.ofFloat(getMinAlpha(), alpha)
+        animator.duration = duration
+        animator.startDelay = startDelay
+        animator.interpolator = interpolator
+        animator.addUpdateListener { updateAlpha(animator.animatedValue as Float, sourceId) }
+        animator.start()
+        animators[sourceId] = animator
+    }
+
+    fun reset() {
+        alphas.clear()
+        animators.forEach { it.value.cancel() }
+        animators.clear()
+        applyAlphaToView()
+    }
+
+    private fun updateAlpha(alpha: Float, sourceId: Int) {
+        alphas[sourceId] = alpha
+        applyAlphaToView()
+    }
+
+    private fun applyAlphaToView() {
+        val minAlpha = getMinAlpha()
+        view.visibility = if (minAlpha != 0f) View.VISIBLE else View.INVISIBLE
+        view.alpha = minAlpha
+    }
+
+    private fun getMinAlpha() = alphas.minOfOrNull { it.value } ?: initialAlpha
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/MobileInputLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/MobileInputLogger.kt
index b3a1c40..051e88f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/MobileInputLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/MobileInputLogger.kt
@@ -23,7 +23,7 @@
 import com.android.settingslib.mobile.MobileMappings
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.log.LogBuffer
-import com.android.systemui.log.LogLevel
+import com.android.systemui.log.core.LogLevel
 import com.android.systemui.statusbar.pipeline.dagger.MobileInputLog
 import javax.inject.Inject
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/MobileViewLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/MobileViewLogger.kt
index 7e0c145..cea6654 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/MobileViewLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/MobileViewLogger.kt
@@ -21,7 +21,7 @@
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dump.DumpManager
 import com.android.systemui.log.LogBuffer
-import com.android.systemui.log.LogLevel
+import com.android.systemui.log.core.LogLevel
 import com.android.systemui.statusbar.pipeline.dagger.MobileViewLog
 import com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel.LocationBasedMobileViewModel
 import java.io.PrintWriter
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/VerboseMobileViewLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/VerboseMobileViewLogger.kt
index 507549b..f4c5723 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/VerboseMobileViewLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/VerboseMobileViewLogger.kt
@@ -20,7 +20,7 @@
 import com.android.systemui.common.shared.model.Icon
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.log.LogBuffer
-import com.android.systemui.log.LogLevel
+import com.android.systemui.log.core.LogLevel
 import com.android.systemui.statusbar.pipeline.dagger.VerboseMobileViewLog
 import com.android.systemui.statusbar.pipeline.mobile.ui.MobileViewLogger.Companion.getIdForLogging
 import com.android.systemui.statusbar.pipeline.mobile.ui.model.SignalIconModel
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ConnectivityInputLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ConnectivityInputLogger.kt
index cac0ae3..8a4d14e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ConnectivityInputLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ConnectivityInputLogger.kt
@@ -20,7 +20,7 @@
 import android.net.NetworkCapabilities
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.log.LogBuffer
-import com.android.systemui.log.LogLevel
+import com.android.systemui.log.core.LogLevel
 import com.android.systemui.statusbar.pipeline.dagger.SharedConnectivityInputLog
 import com.android.systemui.statusbar.pipeline.shared.data.model.DefaultConnectionModel
 import javax.inject.Inject
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/LoggerHelper.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/LoggerHelper.kt
index 328d901..4b9de85 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/LoggerHelper.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/LoggerHelper.kt
@@ -19,7 +19,7 @@
 import android.net.Network
 import android.net.NetworkCapabilities
 import com.android.systemui.log.LogBuffer
-import com.android.systemui.log.LogLevel
+import com.android.systemui.log.core.LogLevel
 
 /** Helper object for logs that are shared between wifi and mobile. */
 object LoggerHelper {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/data/model/DefaultConnectionModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/data/model/DefaultConnectionModel.kt
index 058eda4..9a504c9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/data/model/DefaultConnectionModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/data/model/DefaultConnectionModel.kt
@@ -17,7 +17,7 @@
 package com.android.systemui.statusbar.pipeline.shared.data.model
 
 import android.net.NetworkCapabilities
-import com.android.systemui.log.LogMessage
+import com.android.systemui.log.core.LogMessage
 
 /**
  * A model for all of the current default connections(s).
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/shared/WifiInputLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/shared/WifiInputLogger.kt
index 4a9ceac..f244376 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/shared/WifiInputLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/shared/WifiInputLogger.kt
@@ -20,7 +20,7 @@
 import android.net.NetworkCapabilities
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.log.LogBuffer
-import com.android.systemui.log.LogLevel
+import com.android.systemui.log.core.LogLevel
 import com.android.systemui.statusbar.pipeline.dagger.WifiInputLog
 import com.android.systemui.statusbar.pipeline.shared.LoggerHelper
 import javax.inject.Inject
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceStateRotationLockSettingControllerLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceStateRotationLockSettingControllerLogger.kt
index 6ba2a81..096ad1f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceStateRotationLockSettingControllerLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceStateRotationLockSettingControllerLogger.kt
@@ -22,7 +22,7 @@
 import android.provider.Settings.Secure.DEVICE_STATE_ROTATION_LOCK_UNLOCKED
 import com.android.internal.R
 import com.android.systemui.log.LogBuffer
-import com.android.systemui.log.LogLevel.VERBOSE
+import com.android.systemui.log.core.LogLevel.VERBOSE
 import com.android.systemui.log.dagger.DeviceStateAutoRotationLog
 import javax.inject.Inject
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManagerLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManagerLogger.kt
index 06ed1fd..175473f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManagerLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManagerLogger.kt
@@ -18,8 +18,8 @@
 
 import com.android.systemui.log.dagger.NotificationHeadsUpLog
 import com.android.systemui.log.LogBuffer
-import com.android.systemui.log.LogLevel.INFO
-import com.android.systemui.log.LogLevel.VERBOSE
+import com.android.systemui.log.core.LogLevel.INFO
+import com.android.systemui.log.core.LogLevel.VERBOSE
 import com.android.systemui.statusbar.notification.collection.NotificationEntry
 import com.android.systemui.statusbar.notification.logKey
 import javax.inject.Inject
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 a82646a..710588c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateControllerImpl.java
@@ -16,6 +16,8 @@
 
 package com.android.systemui.statusbar.policy;
 
+import static android.hardware.biometrics.BiometricSourceType.FACE;
+
 import android.annotation.NonNull;
 import android.content.BroadcastReceiver;
 import android.content.Context;
@@ -199,6 +201,11 @@
         Trace.endSection();
     }
 
+    private void notifyKeyguardFaceAuthEnabledChanged() {
+        // Copy the list to allow removal during callback.
+        new ArrayList<>(mCallbacks).forEach(Callback::onFaceAuthEnabledChanged);
+    }
+
     private void notifyUnlockedChanged() {
         Trace.beginSection("KeyguardStateController#notifyUnlockedChanged");
         // Copy the list to allow removal during callback.
@@ -419,6 +426,16 @@
         }
 
         @Override
+        public void onBiometricEnrollmentStateChanged(BiometricSourceType biometricSourceType) {
+            if (biometricSourceType == FACE) {
+                // We only care about enrollment state here. Keyguard face auth enabled is just
+                // same as face auth enrolled
+                update(false);
+                notifyKeyguardFaceAuthEnabledChanged();
+            }
+        }
+
+        @Override
         public void onStartedWakingUp() {
             update(false /* updateAlways */);
         }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyStateInflater.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyStateInflater.kt
index 21d0338..cac5e32 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyStateInflater.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyStateInflater.kt
@@ -22,6 +22,13 @@
 import android.app.RemoteInput
 import android.content.Context
 import android.content.Intent
+import android.graphics.Bitmap
+import android.graphics.ImageDecoder
+import android.graphics.drawable.AdaptiveIconDrawable
+import android.graphics.drawable.BitmapDrawable
+import android.graphics.drawable.Drawable
+import android.graphics.drawable.GradientDrawable
+import android.graphics.drawable.Icon
 import android.os.Build
 import android.os.Bundle
 import android.os.SystemClock
@@ -48,7 +55,13 @@
 import com.android.systemui.statusbar.policy.SmartReplyView.SmartActions
 import com.android.systemui.statusbar.policy.SmartReplyView.SmartButtonType
 import com.android.systemui.statusbar.policy.SmartReplyView.SmartReplies
+import java.util.concurrent.FutureTask
+import java.util.concurrent.SynchronousQueue
+import java.util.concurrent.ThreadPoolExecutor
+import java.util.concurrent.TimeUnit
 import javax.inject.Inject
+import kotlin.system.measureTimeMillis
+
 
 /** Returns whether we should show the smart reply view and its smart suggestions. */
 fun shouldShowSmartReplyView(
@@ -281,6 +294,51 @@
     ): Button
 }
 
+private const val ICON_TASK_TIMEOUT_MS = 500L
+private val iconTaskThreadPool = ThreadPoolExecutor(0, 25, 1, TimeUnit.MINUTES, SynchronousQueue())
+
+private fun loadIconDrawableWithTimeout(
+    icon: Icon,
+    packageContext: Context,
+    targetSize: Int,
+): Drawable? {
+    if (icon.type != Icon.TYPE_URI && icon.type != Icon.TYPE_URI_ADAPTIVE_BITMAP) {
+        return icon.loadDrawable(packageContext)
+    }
+    val bitmapTask = FutureTask {
+        val bitmap: Bitmap?
+        val durationMillis = measureTimeMillis {
+            val source = ImageDecoder.createSource(packageContext.contentResolver, icon.uri)
+            bitmap = ImageDecoder.decodeBitmap(source) { decoder, _, _ ->
+                decoder.setTargetSize(targetSize, targetSize)
+                decoder.allocator = ImageDecoder.ALLOCATOR_DEFAULT
+            }
+        }
+        if (durationMillis > ICON_TASK_TIMEOUT_MS) {
+            Log.w(TAG, "Loading $icon took ${durationMillis / 1000f} sec")
+        }
+        checkNotNull(bitmap) { "ImageDecoder.decodeBitmap() returned null" }
+    }
+    val bitmap = runCatching {
+        iconTaskThreadPool.execute(bitmapTask)
+        bitmapTask.get(ICON_TASK_TIMEOUT_MS, TimeUnit.MILLISECONDS)
+    }.getOrElse { ex ->
+        Log.e(TAG, "Failed to load $icon: $ex")
+        bitmapTask.cancel(true)
+        return null
+    }
+    // TODO(b/288561520): rewrite Icon so that we don't need to duplicate this logic
+    val bitmapDrawable = BitmapDrawable(packageContext.resources, bitmap)
+    val result = if (icon.type == Icon.TYPE_URI_ADAPTIVE_BITMAP)
+        AdaptiveIconDrawable(null, bitmapDrawable) else bitmapDrawable
+    if (icon.hasTint()) {
+        result.mutate()
+        result.setTintList(icon.tintList)
+        result.setTintBlendMode(icon.tintBlendMode)
+    }
+    return result
+}
+
 /* internal */ class SmartActionInflaterImpl @Inject constructor(
     private val constants: SmartReplyConstants,
     private val activityStarter: ActivityStarter,
@@ -304,12 +362,12 @@
 
                 // We received the Icon from the application - so use the Context of the application to
                 // reference icon resources.
-                val iconDrawable = action.getIcon().loadDrawable(packageContext)
-                        .apply {
-                            val newIconSize: Int = context.resources.getDimensionPixelSize(
-                                    R.dimen.smart_action_button_icon_size)
-                            setBounds(0, 0, newIconSize, newIconSize)
-                        }
+                val newIconSize = context.resources
+                    .getDimensionPixelSize(R.dimen.smart_action_button_icon_size)
+                val iconDrawable =
+                    loadIconDrawableWithTimeout(action.getIcon(), packageContext, newIconSize)
+                        ?: GradientDrawable()
+                iconDrawable.setBounds(0, 0, newIconSize, newIconSize)
                 // Add the action icon to the Smart Action button.
                 setCompoundDrawablesRelative(iconDrawable, null, null, null)
 
diff --git a/packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewLogger.kt b/packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewLogger.kt
index 066ac04..a9d2029 100644
--- a/packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewLogger.kt
@@ -18,7 +18,7 @@
 
 import android.view.View
 import com.android.systemui.log.LogBuffer
-import com.android.systemui.log.LogLevel
+import com.android.systemui.log.core.LogLevel
 
 /** A logger for temporary view changes -- see [TemporaryViewDisplayController]. */
 open class TemporaryViewLogger<T : TemporaryViewInfo>(
diff --git a/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarLogger.kt b/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarLogger.kt
index d55751b..6706873 100644
--- a/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarLogger.kt
@@ -18,7 +18,7 @@
 
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.log.LogBuffer
-import com.android.systemui.log.LogLevel
+import com.android.systemui.log.core.LogLevel
 import com.android.systemui.temporarydisplay.TemporaryViewLogger
 import com.android.systemui.temporarydisplay.dagger.ChipbarLog
 import javax.inject.Inject
diff --git a/packages/SystemUI/src/com/android/systemui/toast/ToastLogger.kt b/packages/SystemUI/src/com/android/systemui/toast/ToastLogger.kt
index dfe748a..c109eb4 100644
--- a/packages/SystemUI/src/com/android/systemui/toast/ToastLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/toast/ToastLogger.kt
@@ -18,9 +18,9 @@
 
 import com.android.systemui.log.dagger.ToastLog
 import com.android.systemui.log.LogBuffer
-import com.android.systemui.log.LogLevel
-import com.android.systemui.log.LogLevel.DEBUG
-import com.android.systemui.log.LogMessage
+import com.android.systemui.log.core.LogLevel
+import com.android.systemui.log.core.LogLevel.DEBUG
+import com.android.systemui.log.core.LogMessage
 import javax.inject.Inject
 
 private const val TAG = "ToastLog"
diff --git a/packages/SystemUI/src/com/android/systemui/util/wakelock/WakeLockLogger.kt b/packages/SystemUI/src/com/android/systemui/util/wakelock/WakeLockLogger.kt
index 0926800..d69b10f 100644
--- a/packages/SystemUI/src/com/android/systemui/util/wakelock/WakeLockLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/util/wakelock/WakeLockLogger.kt
@@ -18,7 +18,7 @@
 
 import android.os.PowerManager
 import com.android.systemui.log.LogBuffer
-import com.android.systemui.log.LogLevel
+import com.android.systemui.log.core.LogLevel
 import javax.inject.Inject
 
 class WakeLockLogger @Inject constructor(@WakeLockLog private val buffer: LogBuffer) {
diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java b/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
index 4c7e6b0..5144d19 100644
--- a/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
+++ b/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
@@ -334,10 +334,8 @@
             }
 
             @Override
-            public void setImeWindowStatus(int displayId, IBinder token,
-                    @InputMethodService.ImeWindowVisibility int vis,
-                    @InputMethodService.BackDispositionMode int backDisposition,
-                    boolean showImeSwitcher) {
+            public void setImeWindowStatus(int displayId, IBinder token, int vis,
+                    int backDisposition, boolean showImeSwitcher) {
                 if (displayId == mDisplayTracker.getDefaultDisplayId()
                         && (vis & InputMethodService.IME_VISIBLE) != 0) {
                     oneHanded.stopOneHanded(
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinViewControllerTest.kt
index d3b4190..5a56baf 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinViewControllerTest.kt
@@ -97,21 +97,7 @@
         `when`(keyguardPinView.findViewById<NumPadButton>(R.id.delete_button))
             .thenReturn(deleteButton)
         `when`(keyguardPinView.findViewById<View>(R.id.key_enter)).thenReturn(enterButton)
-        pinViewController =
-            KeyguardPinViewController(
-                keyguardPinView,
-                keyguardUpdateMonitor,
-                securityMode,
-                lockPatternUtils,
-                mKeyguardSecurityCallback,
-                keyguardMessageAreaControllerFactory,
-                mLatencyTracker,
-                liftToActivateListener,
-                mEmergencyButtonController,
-                falsingCollector,
-                postureController,
-                featureFlags
-            )
+        constructViewController()
     }
 
     @Test
@@ -135,8 +121,10 @@
         `when`(lockPatternUtils.isAutoPinConfirmEnabled(anyInt())).thenReturn(true)
         `when`(lockPatternUtils.getCurrentFailedPasswordAttempts(anyInt())).thenReturn(3)
         `when`(passwordTextView.text).thenReturn("")
+        constructViewController()
 
         pinViewController.startAppearAnimation()
+
         verify(deleteButton).visibility = View.INVISIBLE
         verify(enterButton).visibility = View.INVISIBLE
         verify(passwordTextView).setUsePinShapes(true)
@@ -150,8 +138,10 @@
         `when`(lockPatternUtils.isAutoPinConfirmEnabled(anyInt())).thenReturn(true)
         `when`(lockPatternUtils.getCurrentFailedPasswordAttempts(anyInt())).thenReturn(6)
         `when`(passwordTextView.text).thenReturn("")
+        constructViewController()
 
         pinViewController.startAppearAnimation()
+
         verify(deleteButton).visibility = View.VISIBLE
         verify(enterButton).visibility = View.VISIBLE
         verify(passwordTextView).setUsePinShapes(true)
@@ -163,4 +153,22 @@
         pinViewController.handleAttemptLockout(0)
         verify(lockPatternUtils).getCurrentFailedPasswordAttempts(anyInt())
     }
+
+    fun constructViewController() {
+        pinViewController =
+            KeyguardPinViewController(
+                keyguardPinView,
+                keyguardUpdateMonitor,
+                securityMode,
+                lockPatternUtils,
+                mKeyguardSecurityCallback,
+                keyguardMessageAreaControllerFactory,
+                mLatencyTracker,
+                liftToActivateListener,
+                mEmergencyButtonController,
+                falsingCollector,
+                postureController,
+                featureFlags
+            )
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
index 901c3fb..4263091 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
@@ -83,6 +83,7 @@
 import android.content.pm.UserInfo;
 import android.database.ContentObserver;
 import android.hardware.SensorPrivacyManager;
+import android.hardware.biometrics.BiometricAuthenticator;
 import android.hardware.biometrics.BiometricConstants;
 import android.hardware.biometrics.BiometricManager;
 import android.hardware.biometrics.BiometricSourceType;
@@ -3000,6 +3001,24 @@
                 TelephonyManager.SIM_STATE_UNKNOWN);
     }
 
+    @Test
+    public void onAuthEnrollmentChangesCallbacksAreNotified() {
+        KeyguardUpdateMonitorCallback callback = mock(KeyguardUpdateMonitorCallback.class);
+        ArgumentCaptor<AuthController.Callback> authCallback = ArgumentCaptor.forClass(
+                AuthController.Callback.class);
+        verify(mAuthController).addCallback(authCallback.capture());
+
+        mKeyguardUpdateMonitor.registerCallback(callback);
+
+        authCallback.getValue().onEnrollmentsChanged(TYPE_FINGERPRINT);
+        mTestableLooper.processAllMessages();
+        verify(callback).onBiometricEnrollmentStateChanged(BiometricSourceType.FINGERPRINT);
+
+        authCallback.getValue().onEnrollmentsChanged(BiometricAuthenticator.TYPE_FACE);
+        mTestableLooper.processAllMessages();
+        verify(callback).onBiometricEnrollmentStateChanged(BiometricSourceType.FACE);
+    }
+
     private void verifyFingerprintAuthenticateNeverCalled() {
         verify(mFingerprintManager, never()).authenticate(any(), any(), any(), any(), any());
         verify(mFingerprintManager, never()).authenticate(any(), any(), any(), any(), anyInt(),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractorTest.kt
index ac2d492..ea3289c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractorTest.kt
@@ -25,7 +25,6 @@
 import com.android.systemui.scene.SceneTestUtils
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.test.TestScope
 import kotlinx.coroutines.test.runCurrent
 import kotlinx.coroutines.test.runTest
 import org.junit.Test
@@ -37,8 +36,8 @@
 @RunWith(JUnit4::class)
 class AuthenticationInteractorTest : SysuiTestCase() {
 
-    private val testScope = TestScope()
-    private val utils = SceneTestUtils(this, testScope)
+    private val utils = SceneTestUtils(this)
+    private val testScope = utils.testScope
     private val repository: AuthenticationRepository = utils.authenticationRepository()
     private val underTest =
         utils.authenticationInteractor(
@@ -46,47 +45,23 @@
         )
 
     @Test
-    fun authMethod() =
+    fun getAuthenticationMethod() =
         testScope.runTest {
-            val authMethod by collectLastValue(underTest.authenticationMethod)
-            assertThat(authMethod).isEqualTo(AuthenticationMethodModel.Pin(1234))
+            assertThat(underTest.getAuthenticationMethod())
+                .isEqualTo(AuthenticationMethodModel.Pin(1234))
 
-            underTest.setAuthenticationMethod(AuthenticationMethodModel.Password("password"))
-            assertThat(authMethod).isEqualTo(AuthenticationMethodModel.Password("password"))
+            utils.authenticationRepository.setAuthenticationMethod(
+                AuthenticationMethodModel.Password("password")
+            )
+            assertThat(underTest.getAuthenticationMethod())
+                .isEqualTo(AuthenticationMethodModel.Password("password"))
         }
 
     @Test
     fun isUnlocked_whenAuthMethodIsNone_isTrue() =
         testScope.runTest {
+            utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.None)
             val isUnlocked by collectLastValue(underTest.isUnlocked)
-            assertThat(isUnlocked).isFalse()
-
-            underTest.setAuthenticationMethod(AuthenticationMethodModel.None)
-
-            assertThat(isUnlocked).isTrue()
-        }
-
-    @Test
-    fun unlockDevice() =
-        testScope.runTest {
-            val isUnlocked by collectLastValue(underTest.isUnlocked)
-            assertThat(isUnlocked).isFalse()
-
-            underTest.unlockDevice()
-            runCurrent()
-
-            assertThat(isUnlocked).isTrue()
-        }
-
-    @Test
-    fun biometricUnlock() =
-        testScope.runTest {
-            val isUnlocked by collectLastValue(underTest.isUnlocked)
-            assertThat(isUnlocked).isFalse()
-
-            underTest.biometricUnlock()
-            runCurrent()
-
             assertThat(isUnlocked).isTrue()
         }
 
@@ -106,9 +81,11 @@
     @Test
     fun isAuthenticationRequired_lockedAndSecured_true() =
         testScope.runTest {
-            underTest.lockDevice()
+            utils.authenticationRepository.setUnlocked(false)
             runCurrent()
-            underTest.setAuthenticationMethod(AuthenticationMethodModel.Password("password"))
+            utils.authenticationRepository.setAuthenticationMethod(
+                AuthenticationMethodModel.Password("password")
+            )
 
             assertThat(underTest.isAuthenticationRequired()).isTrue()
         }
@@ -116,9 +93,9 @@
     @Test
     fun isAuthenticationRequired_lockedAndNotSecured_false() =
         testScope.runTest {
-            underTest.lockDevice()
+            utils.authenticationRepository.setUnlocked(false)
             runCurrent()
-            underTest.setAuthenticationMethod(AuthenticationMethodModel.Swipe)
+            utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Swipe)
 
             assertThat(underTest.isAuthenticationRequired()).isFalse()
         }
@@ -126,9 +103,11 @@
     @Test
     fun isAuthenticationRequired_unlockedAndSecured_false() =
         testScope.runTest {
-            underTest.unlockDevice()
+            utils.authenticationRepository.setUnlocked(true)
             runCurrent()
-            underTest.setAuthenticationMethod(AuthenticationMethodModel.Password("password"))
+            utils.authenticationRepository.setAuthenticationMethod(
+                AuthenticationMethodModel.Password("password")
+            )
 
             assertThat(underTest.isAuthenticationRequired()).isFalse()
         }
@@ -136,67 +115,63 @@
     @Test
     fun isAuthenticationRequired_unlockedAndNotSecured_false() =
         testScope.runTest {
-            underTest.unlockDevice()
+            utils.authenticationRepository.setUnlocked(true)
             runCurrent()
-            underTest.setAuthenticationMethod(AuthenticationMethodModel.Swipe)
+            utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Swipe)
 
             assertThat(underTest.isAuthenticationRequired()).isFalse()
         }
 
     @Test
-    fun authenticate_withCorrectPin_returnsTrueAndUnlocksDevice() =
+    fun authenticate_withCorrectPin_returnsTrue() =
         testScope.runTest {
             val failedAttemptCount by collectLastValue(underTest.failedAuthenticationAttempts)
-            val isUnlocked by collectLastValue(underTest.isUnlocked)
-            underTest.setAuthenticationMethod(AuthenticationMethodModel.Pin(1234))
-            assertThat(isUnlocked).isFalse()
+            utils.authenticationRepository.setAuthenticationMethod(
+                AuthenticationMethodModel.Pin(1234)
+            )
 
             assertThat(underTest.authenticate(listOf(1, 2, 3, 4))).isTrue()
-            assertThat(isUnlocked).isTrue()
             assertThat(failedAttemptCount).isEqualTo(0)
         }
 
     @Test
-    fun authenticate_withIncorrectPin_returnsFalseAndDoesNotUnlockDevice() =
+    fun authenticate_withIncorrectPin_returnsFalse() =
         testScope.runTest {
             val failedAttemptCount by collectLastValue(underTest.failedAuthenticationAttempts)
-            val isUnlocked by collectLastValue(underTest.isUnlocked)
-            underTest.setAuthenticationMethod(AuthenticationMethodModel.Pin(1234))
-            assertThat(isUnlocked).isFalse()
+            utils.authenticationRepository.setAuthenticationMethod(
+                AuthenticationMethodModel.Pin(1234)
+            )
 
             assertThat(underTest.authenticate(listOf(9, 8, 7))).isFalse()
-            assertThat(isUnlocked).isFalse()
             assertThat(failedAttemptCount).isEqualTo(1)
         }
 
     @Test
-    fun authenticate_withEmptyPin_returnsFalseAndDoesNotUnlockDevice() =
+    fun authenticate_withEmptyPin_returnsFalse() =
         testScope.runTest {
             val failedAttemptCount by collectLastValue(underTest.failedAuthenticationAttempts)
-            val isUnlocked by collectLastValue(underTest.isUnlocked)
-            underTest.setAuthenticationMethod(AuthenticationMethodModel.Pin(1234))
-            assertThat(isUnlocked).isFalse()
+            utils.authenticationRepository.setAuthenticationMethod(
+                AuthenticationMethodModel.Pin(1234)
+            )
 
             assertThat(underTest.authenticate(listOf())).isFalse()
-            assertThat(isUnlocked).isFalse()
             assertThat(failedAttemptCount).isEqualTo(1)
         }
 
     @Test
-    fun authenticate_withCorrectMaxLengthPin_returnsTrueAndUnlocksDevice() =
+    fun authenticate_withCorrectMaxLengthPin_returnsTrue() =
         testScope.runTest {
             val failedAttemptCount by collectLastValue(underTest.failedAuthenticationAttempts)
-            val isUnlocked by collectLastValue(underTest.isUnlocked)
-            underTest.setAuthenticationMethod(AuthenticationMethodModel.Pin(9999999999999999))
-            assertThat(isUnlocked).isFalse()
+            utils.authenticationRepository.setAuthenticationMethod(
+                AuthenticationMethodModel.Pin(9999999999999999)
+            )
 
             assertThat(underTest.authenticate(List(16) { 9 })).isTrue()
-            assertThat(isUnlocked).isTrue()
             assertThat(failedAttemptCount).isEqualTo(0)
         }
 
     @Test
-    fun authenticate_withCorrectTooLongPin_returnsFalseAndDoesNotUnlockDevice() =
+    fun authenticate_withCorrectTooLongPin_returnsFalse() =
         testScope.runTest {
             // Max pin length is 16 digits. To avoid issues with overflows, this test ensures
             // that all pins > 16 decimal digits are rejected.
@@ -205,47 +180,43 @@
             assertThat(DevicePolicyManager.MAX_PASSWORD_LENGTH).isLessThan(17)
 
             val failedAttemptCount by collectLastValue(underTest.failedAuthenticationAttempts)
-            val isUnlocked by collectLastValue(underTest.isUnlocked)
-            underTest.setAuthenticationMethod(AuthenticationMethodModel.Pin(99999999999999999))
-            assertThat(isUnlocked).isFalse()
+            utils.authenticationRepository.setAuthenticationMethod(
+                AuthenticationMethodModel.Pin(99999999999999999)
+            )
 
             assertThat(underTest.authenticate(List(17) { 9 })).isFalse()
-            assertThat(isUnlocked).isFalse()
             assertThat(failedAttemptCount).isEqualTo(1)
         }
 
     @Test
-    fun authenticate_withCorrectPassword_returnsTrueAndUnlocksDevice() =
+    fun authenticate_withCorrectPassword_returnsTrue() =
         testScope.runTest {
             val failedAttemptCount by collectLastValue(underTest.failedAuthenticationAttempts)
-            val isUnlocked by collectLastValue(underTest.isUnlocked)
-            underTest.setAuthenticationMethod(AuthenticationMethodModel.Password("password"))
-            assertThat(isUnlocked).isFalse()
+            utils.authenticationRepository.setAuthenticationMethod(
+                AuthenticationMethodModel.Password("password")
+            )
 
             assertThat(underTest.authenticate("password".toList())).isTrue()
-            assertThat(isUnlocked).isTrue()
             assertThat(failedAttemptCount).isEqualTo(0)
         }
 
     @Test
-    fun authenticate_withIncorrectPassword_returnsFalseAndDoesNotUnlockDevice() =
+    fun authenticate_withIncorrectPassword_returnsFalse() =
         testScope.runTest {
             val failedAttemptCount by collectLastValue(underTest.failedAuthenticationAttempts)
-            val isUnlocked by collectLastValue(underTest.isUnlocked)
-            underTest.setAuthenticationMethod(AuthenticationMethodModel.Password("password"))
-            assertThat(isUnlocked).isFalse()
+            utils.authenticationRepository.setAuthenticationMethod(
+                AuthenticationMethodModel.Password("password")
+            )
 
             assertThat(underTest.authenticate("alohomora".toList())).isFalse()
-            assertThat(isUnlocked).isFalse()
             assertThat(failedAttemptCount).isEqualTo(1)
         }
 
     @Test
-    fun authenticate_withCorrectPattern_returnsTrueAndUnlocksDevice() =
+    fun authenticate_withCorrectPattern_returnsTrue() =
         testScope.runTest {
             val failedAttemptCount by collectLastValue(underTest.failedAuthenticationAttempts)
-            val isUnlocked by collectLastValue(underTest.isUnlocked)
-            underTest.setAuthenticationMethod(
+            utils.authenticationRepository.setAuthenticationMethod(
                 AuthenticationMethodModel.Pattern(
                     listOf(
                         AuthenticationMethodModel.Pattern.PatternCoordinate(
@@ -263,7 +234,6 @@
                     )
                 )
             )
-            assertThat(isUnlocked).isFalse()
 
             assertThat(
                     underTest.authenticate(
@@ -284,16 +254,14 @@
                     )
                 )
                 .isTrue()
-            assertThat(isUnlocked).isTrue()
             assertThat(failedAttemptCount).isEqualTo(0)
         }
 
     @Test
-    fun authenticate_withIncorrectPattern_returnsFalseAndDoesNotUnlockDevice() =
+    fun authenticate_withIncorrectPattern_returnsFalse() =
         testScope.runTest {
             val failedAttemptCount by collectLastValue(underTest.failedAuthenticationAttempts)
-            val isUnlocked by collectLastValue(underTest.isUnlocked)
-            underTest.setAuthenticationMethod(
+            utils.authenticationRepository.setAuthenticationMethod(
                 AuthenticationMethodModel.Pattern(
                     listOf(
                         AuthenticationMethodModel.Pattern.PatternCoordinate(
@@ -311,7 +279,6 @@
                     )
                 )
             )
-            assertThat(isUnlocked).isFalse()
 
             assertThat(
                     underTest.authenticate(
@@ -332,22 +299,18 @@
                     )
                 )
                 .isFalse()
-            assertThat(isUnlocked).isFalse()
             assertThat(failedAttemptCount).isEqualTo(1)
         }
 
     @Test
-    fun tryAutoConfirm_withAutoConfirmPinAndEmptyInput_returnsNullAndHasNoEffect() =
+    fun tryAutoConfirm_withAutoConfirmPinAndEmptyInput_returnsNull() =
         testScope.runTest {
             val failedAttemptCount by collectLastValue(underTest.failedAuthenticationAttempts)
-            val isUnlocked by collectLastValue(underTest.isUnlocked)
-            underTest.setAuthenticationMethod(
+            utils.authenticationRepository.setAuthenticationMethod(
                 AuthenticationMethodModel.Pin(1234, autoConfirm = true)
             )
-            assertThat(isUnlocked).isFalse()
 
             assertThat(underTest.authenticate(listOf(), tryAutoConfirm = true)).isNull()
-            assertThat(isUnlocked).isFalse()
             assertThat(failedAttemptCount).isEqualTo(0)
         }
 
@@ -355,14 +318,11 @@
     fun tryAutoConfirm_withAutoConfirmPinAndShorterPin_returnsNullAndHasNoEffect() =
         testScope.runTest {
             val failedAttemptCount by collectLastValue(underTest.failedAuthenticationAttempts)
-            val isUnlocked by collectLastValue(underTest.isUnlocked)
-            underTest.setAuthenticationMethod(
+            utils.authenticationRepository.setAuthenticationMethod(
                 AuthenticationMethodModel.Pin(1234, autoConfirm = true)
             )
-            assertThat(isUnlocked).isFalse()
 
             assertThat(underTest.authenticate(listOf(1, 2, 3), tryAutoConfirm = true)).isNull()
-            assertThat(isUnlocked).isFalse()
             assertThat(failedAttemptCount).isEqualTo(0)
         }
 
@@ -370,14 +330,11 @@
     fun tryAutoConfirm_withAutoConfirmWrongPinCorrectLength_returnsFalseAndDoesNotUnlockDevice() =
         testScope.runTest {
             val failedAttemptCount by collectLastValue(underTest.failedAuthenticationAttempts)
-            val isUnlocked by collectLastValue(underTest.isUnlocked)
-            underTest.setAuthenticationMethod(
+            utils.authenticationRepository.setAuthenticationMethod(
                 AuthenticationMethodModel.Pin(1234, autoConfirm = true)
             )
-            assertThat(isUnlocked).isFalse()
 
             assertThat(underTest.authenticate(listOf(1, 2, 4, 4), tryAutoConfirm = true)).isFalse()
-            assertThat(isUnlocked).isFalse()
             assertThat(failedAttemptCount).isEqualTo(1)
         }
 
@@ -385,15 +342,12 @@
     fun tryAutoConfirm_withAutoConfirmLongerPin_returnsFalseAndDoesNotUnlockDevice() =
         testScope.runTest {
             val failedAttemptCount by collectLastValue(underTest.failedAuthenticationAttempts)
-            val isUnlocked by collectLastValue(underTest.isUnlocked)
-            underTest.setAuthenticationMethod(
+            utils.authenticationRepository.setAuthenticationMethod(
                 AuthenticationMethodModel.Pin(1234, autoConfirm = true)
             )
-            assertThat(isUnlocked).isFalse()
 
             assertThat(underTest.authenticate(listOf(1, 2, 3, 4, 5), tryAutoConfirm = true))
                 .isFalse()
-            assertThat(isUnlocked).isFalse()
             assertThat(failedAttemptCount).isEqualTo(1)
         }
 
@@ -401,14 +355,11 @@
     fun tryAutoConfirm_withAutoConfirmCorrectPin_returnsTrueAndUnlocksDevice() =
         testScope.runTest {
             val failedAttemptCount by collectLastValue(underTest.failedAuthenticationAttempts)
-            val isUnlocked by collectLastValue(underTest.isUnlocked)
-            underTest.setAuthenticationMethod(
+            utils.authenticationRepository.setAuthenticationMethod(
                 AuthenticationMethodModel.Pin(1234, autoConfirm = true)
             )
-            assertThat(isUnlocked).isFalse()
 
             assertThat(underTest.authenticate(listOf(1, 2, 4, 4), tryAutoConfirm = true)).isFalse()
-            assertThat(isUnlocked).isFalse()
             assertThat(failedAttemptCount).isEqualTo(1)
         }
 
@@ -416,14 +367,11 @@
     fun tryAutoConfirm_withoutAutoConfirmButCorrectPin_returnsNullAndHasNoEffects() =
         testScope.runTest {
             val failedAttemptCount by collectLastValue(underTest.failedAuthenticationAttempts)
-            val isUnlocked by collectLastValue(underTest.isUnlocked)
-            underTest.setAuthenticationMethod(
+            utils.authenticationRepository.setAuthenticationMethod(
                 AuthenticationMethodModel.Pin(1234, autoConfirm = false)
             )
-            assertThat(isUnlocked).isFalse()
 
             assertThat(underTest.authenticate(listOf(1, 2, 3, 4), tryAutoConfirm = true)).isNull()
-            assertThat(isUnlocked).isFalse()
             assertThat(failedAttemptCount).isEqualTo(0)
         }
 
@@ -431,23 +379,11 @@
     fun tryAutoConfirm_withoutCorrectPassword_returnsNullAndHasNoEffects() =
         testScope.runTest {
             val failedAttemptCount by collectLastValue(underTest.failedAuthenticationAttempts)
-            val isUnlocked by collectLastValue(underTest.isUnlocked)
-            underTest.setAuthenticationMethod(AuthenticationMethodModel.Password("password"))
-            assertThat(isUnlocked).isFalse()
+            utils.authenticationRepository.setAuthenticationMethod(
+                AuthenticationMethodModel.Password("password")
+            )
 
             assertThat(underTest.authenticate("password".toList(), tryAutoConfirm = true)).isNull()
-            assertThat(isUnlocked).isFalse()
             assertThat(failedAttemptCount).isEqualTo(0)
         }
-
-    @Test
-    fun unlocksDevice_whenAuthMethodBecomesNone() =
-        testScope.runTest {
-            val isUnlocked by collectLastValue(underTest.isUnlocked)
-            assertThat(isUnlocked).isFalse()
-
-            repository.setAuthenticationMethod(AuthenticationMethodModel.None)
-
-            assertThat(isUnlocked).isTrue()
-        }
 }
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 821c2cb..30e5447 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java
@@ -1316,12 +1316,22 @@
         // WHEN ACTION_DOWN is received
         when(mSinglePointerTouchProcessor.processTouch(any(), anyInt(), any())).thenReturn(
                 processorResultDown);
-        MotionEvent downEvent = MotionEvent.obtain(0, 0, ACTION_DOWN, 0, 0, 0);
-        mTouchListenerCaptor.getValue().onTouch(mUdfpsView, downEvent);
+        MotionEvent event = MotionEvent.obtain(0, 0, ACTION_DOWN, 0, 0, 0);
+        mTouchListenerCaptor.getValue().onTouch(mUdfpsView, event);
         mBiometricExecutor.runAllReady();
-        downEvent.recycle();
 
-        // THEN the touch is pilfered
+        // WHEN ACTION_MOVE is received after
+        final TouchProcessorResult processorResultUnchanged =
+                new TouchProcessorResult.ProcessedTouch(
+                        InteractionEvent.UNCHANGED, 1 /* pointerId */, touchData);
+        when(mSinglePointerTouchProcessor.processTouch(any(), anyInt(), any())).thenReturn(
+                processorResultUnchanged);
+        event.setAction(ACTION_MOVE);
+        mTouchListenerCaptor.getValue().onTouch(mUdfpsView, event);
+        mBiometricExecutor.runAllReady();
+        event.recycle();
+
+        // THEN only pilfer once on the initial down
         verify(mInputManager).pilferPointers(any());
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractorTest.kt
index 9483667..d09353b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractorTest.kt
@@ -26,7 +26,6 @@
 import com.android.systemui.scene.shared.model.SceneModel
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.test.TestScope
 import kotlinx.coroutines.test.advanceTimeBy
 import kotlinx.coroutines.test.runCurrent
 import kotlinx.coroutines.test.runTest
@@ -40,8 +39,8 @@
 @RunWith(JUnit4::class)
 class BouncerInteractorTest : SysuiTestCase() {
 
-    private val testScope = TestScope()
-    private val utils = SceneTestUtils(this, testScope)
+    private val utils = SceneTestUtils(this)
+    private val testScope = utils.testScope
     private val authenticationInteractor =
         utils.authenticationInteractor(
             repository = utils.authenticationRepository(),
@@ -69,8 +68,10 @@
             val currentScene by collectLastValue(sceneInteractor.currentScene("container1"))
             val message by collectLastValue(underTest.message)
 
-            authenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.Pin(1234))
-            authenticationInteractor.lockDevice()
+            utils.authenticationRepository.setAuthenticationMethod(
+                AuthenticationMethodModel.Pin(1234)
+            )
+            utils.authenticationRepository.setUnlocked(false)
             underTest.showOrUnlockDevice("container1")
             assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
             assertThat(message).isEqualTo(MESSAGE_ENTER_YOUR_PIN)
@@ -100,10 +101,10 @@
             val currentScene by collectLastValue(sceneInteractor.currentScene("container1"))
             val message by collectLastValue(underTest.message)
 
-            authenticationInteractor.setAuthenticationMethod(
+            utils.authenticationRepository.setAuthenticationMethod(
                 AuthenticationMethodModel.Pin(1234, autoConfirm = true)
             )
-            authenticationInteractor.lockDevice()
+            utils.authenticationRepository.setUnlocked(false)
             underTest.showOrUnlockDevice("container1")
             assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
             assertThat(message).isEqualTo(MESSAGE_ENTER_YOUR_PIN)
@@ -130,10 +131,10 @@
             val currentScene by collectLastValue(sceneInteractor.currentScene("container1"))
             val message by collectLastValue(underTest.message)
 
-            authenticationInteractor.setAuthenticationMethod(
+            utils.authenticationRepository.setAuthenticationMethod(
                 AuthenticationMethodModel.Pin(1234, autoConfirm = false)
             )
-            authenticationInteractor.lockDevice()
+            utils.authenticationRepository.setUnlocked(false)
             underTest.showOrUnlockDevice("container1")
             assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
             underTest.clearMessage()
@@ -154,10 +155,10 @@
         testScope.runTest {
             val currentScene by collectLastValue(sceneInteractor.currentScene("container1"))
             val message by collectLastValue(underTest.message)
-            authenticationInteractor.setAuthenticationMethod(
+            utils.authenticationRepository.setAuthenticationMethod(
                 AuthenticationMethodModel.Password("password")
             )
-            authenticationInteractor.lockDevice()
+            utils.authenticationRepository.setUnlocked(false)
             underTest.showOrUnlockDevice("container1")
             assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
             assertThat(message).isEqualTo(MESSAGE_ENTER_YOUR_PASSWORD)
@@ -186,10 +187,10 @@
         testScope.runTest {
             val currentScene by collectLastValue(sceneInteractor.currentScene("container1"))
             val message by collectLastValue(underTest.message)
-            authenticationInteractor.setAuthenticationMethod(
+            utils.authenticationRepository.setAuthenticationMethod(
                 AuthenticationMethodModel.Pattern(emptyList())
             )
-            authenticationInteractor.lockDevice()
+            utils.authenticationRepository.setUnlocked(false)
             underTest.showOrUnlockDevice("container1")
             assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
             assertThat(message).isEqualTo(MESSAGE_ENTER_YOUR_PATTERN)
@@ -222,8 +223,10 @@
     fun showOrUnlockDevice_notLocked_switchesToGoneScene() =
         testScope.runTest {
             val currentScene by collectLastValue(sceneInteractor.currentScene("container1"))
-            authenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.Pin(1234))
-            authenticationInteractor.unlockDevice()
+            utils.authenticationRepository.setAuthenticationMethod(
+                AuthenticationMethodModel.Pin(1234)
+            )
+            utils.authenticationRepository.setUnlocked(true)
             runCurrent()
 
             underTest.showOrUnlockDevice("container1")
@@ -235,8 +238,8 @@
     fun showOrUnlockDevice_authMethodNotSecure_switchesToGoneScene() =
         testScope.runTest {
             val currentScene by collectLastValue(sceneInteractor.currentScene("container1"))
-            authenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.Swipe)
-            authenticationInteractor.lockDevice()
+            utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Swipe)
+            utils.authenticationRepository.setUnlocked(false)
 
             underTest.showOrUnlockDevice("container1")
 
@@ -248,10 +251,10 @@
         testScope.runTest {
             val currentScene by collectLastValue(sceneInteractor.currentScene("container1"))
             val message by collectLastValue(underTest.message)
-            authenticationInteractor.setAuthenticationMethod(
+            utils.authenticationRepository.setAuthenticationMethod(
                 AuthenticationMethodModel.Password("password")
             )
-            authenticationInteractor.lockDevice()
+            utils.authenticationRepository.setUnlocked(false)
 
             val customMessage = "Hello there!"
             underTest.showOrUnlockDevice("container1", customMessage)
@@ -265,11 +268,17 @@
         testScope.runTest {
             val throttling by collectLastValue(underTest.throttling)
             val message by collectLastValue(underTest.message)
-            val isUnlocked by collectLastValue(authenticationInteractor.isUnlocked)
-            authenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.Pin(1234))
+            val currentScene by
+                collectLastValue(sceneInteractor.currentScene(SceneTestUtils.CONTAINER_1))
+            utils.authenticationRepository.setAuthenticationMethod(
+                AuthenticationMethodModel.Pin(1234)
+            )
+            runCurrent()
+            underTest.showOrUnlockDevice(SceneTestUtils.CONTAINER_1)
+            runCurrent()
+            assertThat(currentScene?.key).isEqualTo(SceneKey.Bouncer)
             assertThat(throttling).isNull()
-            assertThat(message).isEqualTo("")
-            assertThat(isUnlocked).isFalse()
+            assertThat(message).isEqualTo(MESSAGE_ENTER_YOUR_PIN)
             repeat(BouncerInteractor.THROTTLE_EVERY) { times ->
                 // Wrong PIN.
                 assertThat(underTest.authenticate(listOf(6, 7, 8, 9))).isFalse()
@@ -280,9 +289,9 @@
             assertThat(throttling).isNotNull()
             assertTryAgainMessage(message, BouncerInteractor.THROTTLE_DURATION_SEC)
 
-            // Correct PIN, but throttled, so doesn't unlock:
+            // Correct PIN, but throttled, so doesn't change away from the bouncer scene:
             assertThat(underTest.authenticate(listOf(1, 2, 3, 4))).isFalse()
-            assertThat(isUnlocked).isFalse()
+            assertThat(currentScene?.key).isEqualTo(SceneKey.Bouncer)
             assertTryAgainMessage(message, BouncerInteractor.THROTTLE_DURATION_SEC)
 
             throttling?.totalDurationSec?.let { seconds ->
@@ -296,11 +305,28 @@
             }
             assertThat(message).isEqualTo("")
             assertThat(throttling).isNull()
-            assertThat(isUnlocked).isFalse()
+            assertThat(currentScene?.key).isEqualTo(SceneKey.Bouncer)
 
-            // Correct PIN and no longer throttled so unlocks:
+            // Correct PIN and no longer throttled so changes to the Gone scene:
             assertThat(underTest.authenticate(listOf(1, 2, 3, 4))).isTrue()
-            assertThat(isUnlocked).isTrue()
+            assertThat(currentScene?.key).isEqualTo(SceneKey.Gone)
+        }
+
+    @Test
+    fun switchesToGone_whenUnlocked() =
+        testScope.runTest {
+            utils.authenticationRepository.setUnlocked(false)
+            sceneInteractor.setCurrentScene(
+                SceneTestUtils.CONTAINER_1,
+                SceneModel(SceneKey.Bouncer)
+            )
+            val currentScene by
+                collectLastValue(sceneInteractor.currentScene(SceneTestUtils.CONTAINER_1))
+            assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
+
+            utils.authenticationRepository.setUnlocked(true)
+
+            assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Gone))
         }
 
     private fun assertTryAgainMessage(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModelTest.kt
index b53e034..22ac1b6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModelTest.kt
@@ -24,7 +24,6 @@
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.test.TestScope
 import kotlinx.coroutines.test.runTest
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -35,8 +34,8 @@
 @RunWith(JUnit4::class)
 class AuthMethodBouncerViewModelTest : SysuiTestCase() {
 
-    private val testScope = TestScope()
-    private val utils = SceneTestUtils(this, testScope)
+    private val utils = SceneTestUtils(this)
+    private val testScope = utils.testScope
     private val authenticationInteractor =
         utils.authenticationInteractor(
             utils.authenticationRepository(),
@@ -55,7 +54,9 @@
     @Test
     fun animateFailure() =
         testScope.runTest {
-            authenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.Pin(1234))
+            utils.authenticationRepository.setAuthenticationMethod(
+                AuthenticationMethodModel.Pin(1234)
+            )
             val animateFailure by collectLastValue(underTest.animateFailure)
             assertThat(animateFailure).isFalse()
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelTest.kt
index c607496..5ffc471 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelTest.kt
@@ -23,11 +23,14 @@
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.scene.SceneTestUtils
 import com.google.common.truth.Truth.assertThat
+import com.google.common.truth.Truth.assertWithMessage
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.flow.emptyFlow
 import kotlinx.coroutines.flow.flatMapLatest
-import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.onEach
 import kotlinx.coroutines.test.advanceTimeBy
+import kotlinx.coroutines.test.runCurrent
 import kotlinx.coroutines.test.runTest
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -38,8 +41,8 @@
 @RunWith(JUnit4::class)
 class BouncerViewModelTest : SysuiTestCase() {
 
-    private val testScope = TestScope()
-    private val utils = SceneTestUtils(this, testScope)
+    private val utils = SceneTestUtils(this)
+    private val testScope = utils.testScope
     private val authenticationInteractor =
         utils.authenticationInteractor(
             repository = utils.authenticationRepository(),
@@ -54,16 +57,26 @@
     @Test
     fun authMethod_nonNullForSecureMethods_nullForNotSecureMethods() =
         testScope.runTest {
-            val authMethodViewModel: AuthMethodBouncerViewModel? by
-                collectLastValue(underTest.authMethod)
+            var authMethodViewModel: AuthMethodBouncerViewModel? = null
+
             authMethodsToTest().forEach { authMethod ->
-                authenticationInteractor.setAuthenticationMethod(authMethod)
+                utils.authenticationRepository.setAuthenticationMethod(authMethod)
+                val job = underTest.authMethod.onEach { authMethodViewModel = it }.launchIn(this)
+                runCurrent()
 
                 if (authMethod.isSecure) {
-                    assertThat(authMethodViewModel).isNotNull()
+                    assertWithMessage("View-model unexpectedly null for auth method $authMethod")
+                        .that(authMethodViewModel)
+                        .isNotNull()
                 } else {
-                    assertThat(authMethodViewModel).isNull()
+                    assertWithMessage(
+                            "View-model unexpectedly non-null for auth method $authMethod"
+                        )
+                        .that(authMethodViewModel)
+                        .isNull()
                 }
+
+                job.cancel()
             }
         }
 
@@ -75,13 +88,13 @@
                 collectLastValue(underTest.authMethod)
             // First pass, populate our "seen" map:
             authMethodsToTest().forEach { authMethod ->
-                authenticationInteractor.setAuthenticationMethod(authMethod)
+                utils.authenticationRepository.setAuthenticationMethod(authMethod)
                 authMethodViewModel?.let { seen[authMethod] = it }
             }
 
             // Second pass, assert same instances are reused:
             authMethodsToTest().forEach { authMethod ->
-                authenticationInteractor.setAuthenticationMethod(authMethod)
+                utils.authenticationRepository.setAuthenticationMethod(authMethod)
                 authMethodViewModel?.let { assertThat(it).isSameInstanceAs(seen[authMethod]) }
             }
         }
@@ -97,7 +110,9 @@
         testScope.runTest {
             val message by collectLastValue(underTest.message)
             val throttling by collectLastValue(bouncerInteractor.throttling)
-            authenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.Pin(1234))
+            utils.authenticationRepository.setAuthenticationMethod(
+                AuthenticationMethodModel.Pin(1234)
+            )
             assertThat(message?.isUpdateAnimated).isTrue()
 
             repeat(BouncerInteractor.THROTTLE_EVERY) {
@@ -120,7 +135,9 @@
                     }
                 )
             val throttling by collectLastValue(bouncerInteractor.throttling)
-            authenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.Pin(1234))
+            utils.authenticationRepository.setAuthenticationMethod(
+                AuthenticationMethodModel.Pin(1234)
+            )
             assertThat(isInputEnabled).isTrue()
 
             repeat(BouncerInteractor.THROTTLE_EVERY) {
@@ -137,7 +154,9 @@
     fun throttlingDialogMessage() =
         testScope.runTest {
             val throttlingDialogMessage by collectLastValue(underTest.throttlingDialogMessage)
-            authenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.Pin(1234))
+            utils.authenticationRepository.setAuthenticationMethod(
+                AuthenticationMethodModel.Pin(1234)
+            )
 
             repeat(BouncerInteractor.THROTTLE_EVERY) {
                 // Wrong PIN.
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModelTest.kt
index f436aa3..699571b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModelTest.kt
@@ -28,7 +28,7 @@
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.asStateFlow
-import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runCurrent
 import kotlinx.coroutines.test.runTest
 import org.junit.Before
 import org.junit.Test
@@ -40,8 +40,8 @@
 @RunWith(JUnit4::class)
 class PasswordBouncerViewModelTest : SysuiTestCase() {
 
-    private val testScope = TestScope()
-    private val utils = SceneTestUtils(this, testScope)
+    private val utils = SceneTestUtils(this)
+    private val testScope = utils.testScope
     private val authenticationInteractor =
         utils.authenticationInteractor(
             repository = utils.authenticationRepository(),
@@ -58,6 +58,7 @@
         )
     private val underTest =
         PasswordBouncerViewModel(
+            applicationScope = testScope.backgroundScope,
             interactor = bouncerInteractor,
             isInputEnabled = MutableStateFlow(true).asStateFlow(),
         )
@@ -71,84 +72,74 @@
     @Test
     fun onShown() =
         testScope.runTest {
-            val isUnlocked by collectLastValue(authenticationInteractor.isUnlocked)
             val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_NAME))
             val message by collectLastValue(bouncerViewModel.message)
             val password by collectLastValue(underTest.password)
-            authenticationInteractor.setAuthenticationMethod(
+            utils.authenticationRepository.setAuthenticationMethod(
                 AuthenticationMethodModel.Password("password")
             )
-            authenticationInteractor.lockDevice()
+            utils.authenticationRepository.setUnlocked(false)
             sceneInteractor.setCurrentScene(CONTAINER_NAME, SceneModel(SceneKey.Bouncer))
-            assertThat(isUnlocked).isFalse()
             assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
 
             underTest.onShown()
 
             assertThat(message?.text).isEqualTo(ENTER_YOUR_PASSWORD)
             assertThat(password).isEqualTo("")
-            assertThat(isUnlocked).isFalse()
             assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
         }
 
     @Test
     fun onPasswordInputChanged() =
         testScope.runTest {
-            val isUnlocked by collectLastValue(authenticationInteractor.isUnlocked)
             val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_NAME))
             val message by collectLastValue(bouncerViewModel.message)
             val password by collectLastValue(underTest.password)
-            authenticationInteractor.setAuthenticationMethod(
+            utils.authenticationRepository.setAuthenticationMethod(
                 AuthenticationMethodModel.Password("password")
             )
-            authenticationInteractor.lockDevice()
+            utils.authenticationRepository.setUnlocked(false)
             sceneInteractor.setCurrentScene(CONTAINER_NAME, SceneModel(SceneKey.Bouncer))
-            assertThat(isUnlocked).isFalse()
             assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
             underTest.onShown()
+            runCurrent()
 
             underTest.onPasswordInputChanged("password")
 
             assertThat(message?.text).isEmpty()
             assertThat(password).isEqualTo("password")
-            assertThat(isUnlocked).isFalse()
             assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
         }
 
     @Test
     fun onAuthenticateKeyPressed_whenCorrect() =
         testScope.runTest {
-            val isUnlocked by collectLastValue(authenticationInteractor.isUnlocked)
             val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_NAME))
-            authenticationInteractor.setAuthenticationMethod(
+            utils.authenticationRepository.setAuthenticationMethod(
                 AuthenticationMethodModel.Password("password")
             )
-            authenticationInteractor.lockDevice()
+            utils.authenticationRepository.setUnlocked(false)
             sceneInteractor.setCurrentScene(CONTAINER_NAME, SceneModel(SceneKey.Bouncer))
-            assertThat(isUnlocked).isFalse()
             assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
             underTest.onShown()
             underTest.onPasswordInputChanged("password")
 
             underTest.onAuthenticateKeyPressed()
 
-            assertThat(isUnlocked).isTrue()
             assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Gone))
         }
 
     @Test
     fun onAuthenticateKeyPressed_whenWrong() =
         testScope.runTest {
-            val isUnlocked by collectLastValue(authenticationInteractor.isUnlocked)
             val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_NAME))
             val message by collectLastValue(bouncerViewModel.message)
             val password by collectLastValue(underTest.password)
-            authenticationInteractor.setAuthenticationMethod(
+            utils.authenticationRepository.setAuthenticationMethod(
                 AuthenticationMethodModel.Password("password")
             )
-            authenticationInteractor.lockDevice()
+            utils.authenticationRepository.setUnlocked(false)
             sceneInteractor.setCurrentScene(CONTAINER_NAME, SceneModel(SceneKey.Bouncer))
-            assertThat(isUnlocked).isFalse()
             assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
             underTest.onShown()
             underTest.onPasswordInputChanged("wrong")
@@ -157,30 +148,26 @@
 
             assertThat(password).isEqualTo("")
             assertThat(message?.text).isEqualTo(WRONG_PASSWORD)
-            assertThat(isUnlocked).isFalse()
             assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
         }
 
     @Test
     fun onAuthenticateKeyPressed_correctAfterWrong() =
         testScope.runTest {
-            val isUnlocked by collectLastValue(authenticationInteractor.isUnlocked)
             val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_NAME))
             val message by collectLastValue(bouncerViewModel.message)
             val password by collectLastValue(underTest.password)
-            authenticationInteractor.setAuthenticationMethod(
+            utils.authenticationRepository.setAuthenticationMethod(
                 AuthenticationMethodModel.Password("password")
             )
-            authenticationInteractor.lockDevice()
+            utils.authenticationRepository.setUnlocked(false)
             sceneInteractor.setCurrentScene(CONTAINER_NAME, SceneModel(SceneKey.Bouncer))
-            assertThat(isUnlocked).isFalse()
             assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
             underTest.onShown()
             underTest.onPasswordInputChanged("wrong")
             underTest.onAuthenticateKeyPressed()
             assertThat(password).isEqualTo("")
             assertThat(message?.text).isEqualTo(WRONG_PASSWORD)
-            assertThat(isUnlocked).isFalse()
             assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
 
             // Enter the correct password:
@@ -189,7 +176,6 @@
 
             underTest.onAuthenticateKeyPressed()
 
-            assertThat(isUnlocked).isTrue()
             assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Gone))
         }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModelTest.kt
index d7d7154..9a1f584 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModelTest.kt
@@ -29,7 +29,7 @@
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.asStateFlow
-import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runCurrent
 import kotlinx.coroutines.test.runTest
 import org.junit.Before
 import org.junit.Test
@@ -41,8 +41,8 @@
 @RunWith(JUnit4::class)
 class PatternBouncerViewModelTest : SysuiTestCase() {
 
-    private val testScope = TestScope()
-    private val utils = SceneTestUtils(this, testScope)
+    private val utils = SceneTestUtils(this)
+    private val testScope = utils.testScope
     private val authenticationInteractor =
         utils.authenticationInteractor(
             repository = utils.authenticationRepository(),
@@ -74,17 +74,15 @@
     @Test
     fun onShown() =
         testScope.runTest {
-            val isUnlocked by collectLastValue(authenticationInteractor.isUnlocked)
             val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_NAME))
             val message by collectLastValue(bouncerViewModel.message)
             val selectedDots by collectLastValue(underTest.selectedDots)
             val currentDot by collectLastValue(underTest.currentDot)
-            authenticationInteractor.setAuthenticationMethod(
+            utils.authenticationRepository.setAuthenticationMethod(
                 AuthenticationMethodModel.Pattern(CORRECT_PATTERN)
             )
-            authenticationInteractor.lockDevice()
+            utils.authenticationRepository.setUnlocked(false)
             sceneInteractor.setCurrentScene(CONTAINER_NAME, SceneModel(SceneKey.Bouncer))
-            assertThat(isUnlocked).isFalse()
             assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
 
             underTest.onShown()
@@ -92,49 +90,44 @@
             assertThat(message?.text).isEqualTo(ENTER_YOUR_PATTERN)
             assertThat(selectedDots).isEmpty()
             assertThat(currentDot).isNull()
-            assertThat(isUnlocked).isFalse()
             assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
         }
 
     @Test
     fun onDragStart() =
         testScope.runTest {
-            val isUnlocked by collectLastValue(authenticationInteractor.isUnlocked)
             val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_NAME))
             val message by collectLastValue(bouncerViewModel.message)
             val selectedDots by collectLastValue(underTest.selectedDots)
             val currentDot by collectLastValue(underTest.currentDot)
-            authenticationInteractor.setAuthenticationMethod(
+            utils.authenticationRepository.setAuthenticationMethod(
                 AuthenticationMethodModel.Pattern(CORRECT_PATTERN)
             )
-            authenticationInteractor.lockDevice()
+            utils.authenticationRepository.setUnlocked(false)
             sceneInteractor.setCurrentScene(CONTAINER_NAME, SceneModel(SceneKey.Bouncer))
-            assertThat(isUnlocked).isFalse()
             assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
             underTest.onShown()
+            runCurrent()
 
             underTest.onDragStart()
 
             assertThat(message?.text).isEmpty()
             assertThat(selectedDots).isEmpty()
             assertThat(currentDot).isNull()
-            assertThat(isUnlocked).isFalse()
             assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
         }
 
     @Test
     fun onDragEnd_whenCorrect() =
         testScope.runTest {
-            val isUnlocked by collectLastValue(authenticationInteractor.isUnlocked)
             val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_NAME))
             val selectedDots by collectLastValue(underTest.selectedDots)
             val currentDot by collectLastValue(underTest.currentDot)
-            authenticationInteractor.setAuthenticationMethod(
+            utils.authenticationRepository.setAuthenticationMethod(
                 AuthenticationMethodModel.Pattern(CORRECT_PATTERN)
             )
-            authenticationInteractor.lockDevice()
+            utils.authenticationRepository.setUnlocked(false)
             sceneInteractor.setCurrentScene(CONTAINER_NAME, SceneModel(SceneKey.Bouncer))
-            assertThat(isUnlocked).isFalse()
             assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
             underTest.onShown()
             underTest.onDragStart()
@@ -168,24 +161,21 @@
 
             underTest.onDragEnd()
 
-            assertThat(isUnlocked).isTrue()
             assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Gone))
         }
 
     @Test
     fun onDragEnd_whenWrong() =
         testScope.runTest {
-            val isUnlocked by collectLastValue(authenticationInteractor.isUnlocked)
             val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_NAME))
             val message by collectLastValue(bouncerViewModel.message)
             val selectedDots by collectLastValue(underTest.selectedDots)
             val currentDot by collectLastValue(underTest.currentDot)
-            authenticationInteractor.setAuthenticationMethod(
+            utils.authenticationRepository.setAuthenticationMethod(
                 AuthenticationMethodModel.Pattern(CORRECT_PATTERN)
             )
-            authenticationInteractor.lockDevice()
+            utils.authenticationRepository.setUnlocked(false)
             sceneInteractor.setCurrentScene(CONTAINER_NAME, SceneModel(SceneKey.Bouncer))
-            assertThat(isUnlocked).isFalse()
             assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
             underTest.onShown()
             underTest.onDragStart()
@@ -203,24 +193,21 @@
             assertThat(selectedDots).isEmpty()
             assertThat(currentDot).isNull()
             assertThat(message?.text).isEqualTo(WRONG_PATTERN)
-            assertThat(isUnlocked).isFalse()
             assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
         }
 
     @Test
     fun onDragEnd_correctAfterWrong() =
         testScope.runTest {
-            val isUnlocked by collectLastValue(authenticationInteractor.isUnlocked)
             val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_NAME))
             val message by collectLastValue(bouncerViewModel.message)
             val selectedDots by collectLastValue(underTest.selectedDots)
             val currentDot by collectLastValue(underTest.currentDot)
-            authenticationInteractor.setAuthenticationMethod(
+            utils.authenticationRepository.setAuthenticationMethod(
                 AuthenticationMethodModel.Pattern(CORRECT_PATTERN)
             )
-            authenticationInteractor.lockDevice()
+            utils.authenticationRepository.setUnlocked(false)
             sceneInteractor.setCurrentScene(CONTAINER_NAME, SceneModel(SceneKey.Bouncer))
-            assertThat(isUnlocked).isFalse()
             assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
             underTest.onShown()
             underTest.onDragStart()
@@ -236,7 +223,6 @@
             assertThat(selectedDots).isEmpty()
             assertThat(currentDot).isNull()
             assertThat(message?.text).isEqualTo(WRONG_PATTERN)
-            assertThat(isUnlocked).isFalse()
             assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
 
             // Enter the correct pattern:
@@ -251,7 +237,6 @@
 
             underTest.onDragEnd()
 
-            assertThat(isUnlocked).isTrue()
             assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Gone))
         }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModelTest.kt
index 7e358d2..61432e2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModelTest.kt
@@ -29,7 +29,7 @@
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.asStateFlow
-import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runCurrent
 import kotlinx.coroutines.test.runTest
 import org.junit.Before
 import org.junit.Test
@@ -41,8 +41,8 @@
 @RunWith(JUnit4::class)
 class PinBouncerViewModelTest : SysuiTestCase() {
 
-    private val testScope = TestScope()
-    private val utils = SceneTestUtils(this, testScope)
+    private val utils = SceneTestUtils(this)
+    private val testScope = utils.testScope
     private val sceneInteractor = utils.sceneInteractor()
     private val authenticationInteractor =
         utils.authenticationInteractor(
@@ -81,60 +81,57 @@
     @Test
     fun onShown() =
         testScope.runTest {
-            val isUnlocked by collectLastValue(authenticationInteractor.isUnlocked)
             val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_NAME))
             val message by collectLastValue(bouncerViewModel.message)
             val entries by collectLastValue(underTest.pinEntries)
-            authenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.Pin(1234))
-            authenticationInteractor.lockDevice()
+            utils.authenticationRepository.setUnlocked(false)
             sceneInteractor.setCurrentScene(CONTAINER_NAME, SceneModel(SceneKey.Bouncer))
-            assertThat(isUnlocked).isFalse()
             assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
 
             underTest.onShown()
 
             assertThat(message?.text).isEqualTo(ENTER_YOUR_PIN)
             assertThat(entries).hasSize(0)
-            assertThat(isUnlocked).isFalse()
             assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
         }
 
     @Test
     fun onPinButtonClicked() =
         testScope.runTest {
-            val isUnlocked by collectLastValue(authenticationInteractor.isUnlocked)
             val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_NAME))
             val message by collectLastValue(bouncerViewModel.message)
             val entries by collectLastValue(underTest.pinEntries)
-            authenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.Pin(1234))
-            authenticationInteractor.lockDevice()
+            utils.authenticationRepository.setAuthenticationMethod(
+                AuthenticationMethodModel.Pin(1234)
+            )
+            utils.authenticationRepository.setUnlocked(false)
             sceneInteractor.setCurrentScene(CONTAINER_NAME, SceneModel(SceneKey.Bouncer))
-            assertThat(isUnlocked).isFalse()
             assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
             underTest.onShown()
+            runCurrent()
 
             underTest.onPinButtonClicked(1)
 
             assertThat(message?.text).isEmpty()
             assertThat(entries).hasSize(1)
             assertThat(entries?.map { it.input }).containsExactly(1)
-            assertThat(isUnlocked).isFalse()
             assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
         }
 
     @Test
     fun onBackspaceButtonClicked() =
         testScope.runTest {
-            val isUnlocked by collectLastValue(authenticationInteractor.isUnlocked)
             val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_NAME))
             val message by collectLastValue(bouncerViewModel.message)
             val entries by collectLastValue(underTest.pinEntries)
-            authenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.Pin(1234))
-            authenticationInteractor.lockDevice()
+            utils.authenticationRepository.setAuthenticationMethod(
+                AuthenticationMethodModel.Pin(1234)
+            )
+            utils.authenticationRepository.setUnlocked(false)
             sceneInteractor.setCurrentScene(CONTAINER_NAME, SceneModel(SceneKey.Bouncer))
-            assertThat(isUnlocked).isFalse()
             assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
             underTest.onShown()
+            runCurrent()
             underTest.onPinButtonClicked(1)
             assertThat(entries).hasSize(1)
 
@@ -142,21 +139,19 @@
 
             assertThat(message?.text).isEmpty()
             assertThat(entries).hasSize(0)
-            assertThat(isUnlocked).isFalse()
             assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
         }
 
     @Test
     fun onPinEdit() =
         testScope.runTest {
-            val isUnlocked by collectLastValue(authenticationInteractor.isUnlocked)
             val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_NAME))
-            val message by collectLastValue(bouncerViewModel.message)
             val entries by collectLastValue(underTest.pinEntries)
-            authenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.Pin(1234))
-            authenticationInteractor.lockDevice()
+            utils.authenticationRepository.setAuthenticationMethod(
+                AuthenticationMethodModel.Pin(1234)
+            )
+            utils.authenticationRepository.setUnlocked(false)
             sceneInteractor.setCurrentScene(CONTAINER_NAME, SceneModel(SceneKey.Bouncer))
-            assertThat(isUnlocked).isFalse()
             assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
             underTest.onShown()
 
@@ -176,16 +171,17 @@
     @Test
     fun onBackspaceButtonLongPressed() =
         testScope.runTest {
-            val isUnlocked by collectLastValue(authenticationInteractor.isUnlocked)
             val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_NAME))
             val message by collectLastValue(bouncerViewModel.message)
             val entries by collectLastValue(underTest.pinEntries)
-            authenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.Pin(1234))
-            authenticationInteractor.lockDevice()
+            utils.authenticationRepository.setAuthenticationMethod(
+                AuthenticationMethodModel.Pin(1234)
+            )
+            utils.authenticationRepository.setUnlocked(false)
             sceneInteractor.setCurrentScene(CONTAINER_NAME, SceneModel(SceneKey.Bouncer))
-            assertThat(isUnlocked).isFalse()
             assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
             underTest.onShown()
+            runCurrent()
             underTest.onPinButtonClicked(1)
             underTest.onPinButtonClicked(2)
             underTest.onPinButtonClicked(3)
@@ -195,19 +191,18 @@
 
             assertThat(message?.text).isEmpty()
             assertThat(entries).hasSize(0)
-            assertThat(isUnlocked).isFalse()
             assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
         }
 
     @Test
     fun onAuthenticateButtonClicked_whenCorrect() =
         testScope.runTest {
-            val isUnlocked by collectLastValue(authenticationInteractor.isUnlocked)
             val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_NAME))
-            authenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.Pin(1234))
-            authenticationInteractor.lockDevice()
+            utils.authenticationRepository.setAuthenticationMethod(
+                AuthenticationMethodModel.Pin(1234)
+            )
+            utils.authenticationRepository.setUnlocked(false)
             sceneInteractor.setCurrentScene(CONTAINER_NAME, SceneModel(SceneKey.Bouncer))
-            assertThat(isUnlocked).isFalse()
             assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
             underTest.onShown()
             underTest.onPinButtonClicked(1)
@@ -217,21 +212,20 @@
 
             underTest.onAuthenticateButtonClicked()
 
-            assertThat(isUnlocked).isTrue()
             assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Gone))
         }
 
     @Test
     fun onAuthenticateButtonClicked_whenWrong() =
         testScope.runTest {
-            val isUnlocked by collectLastValue(authenticationInteractor.isUnlocked)
             val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_NAME))
             val message by collectLastValue(bouncerViewModel.message)
             val entries by collectLastValue(underTest.pinEntries)
-            authenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.Pin(1234))
-            authenticationInteractor.lockDevice()
+            utils.authenticationRepository.setAuthenticationMethod(
+                AuthenticationMethodModel.Pin(1234)
+            )
+            utils.authenticationRepository.setUnlocked(false)
             sceneInteractor.setCurrentScene(CONTAINER_NAME, SceneModel(SceneKey.Bouncer))
-            assertThat(isUnlocked).isFalse()
             assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
             underTest.onShown()
             underTest.onPinButtonClicked(1)
@@ -244,21 +238,20 @@
 
             assertThat(entries).hasSize(0)
             assertThat(message?.text).isEqualTo(WRONG_PIN)
-            assertThat(isUnlocked).isFalse()
             assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
         }
 
     @Test
     fun onAuthenticateButtonClicked_correctAfterWrong() =
         testScope.runTest {
-            val isUnlocked by collectLastValue(authenticationInteractor.isUnlocked)
             val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_NAME))
             val message by collectLastValue(bouncerViewModel.message)
             val entries by collectLastValue(underTest.pinEntries)
-            authenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.Pin(1234))
-            authenticationInteractor.lockDevice()
+            utils.authenticationRepository.setAuthenticationMethod(
+                AuthenticationMethodModel.Pin(1234)
+            )
+            utils.authenticationRepository.setUnlocked(false)
             sceneInteractor.setCurrentScene(CONTAINER_NAME, SceneModel(SceneKey.Bouncer))
-            assertThat(isUnlocked).isFalse()
             assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
             underTest.onShown()
             underTest.onPinButtonClicked(1)
@@ -269,7 +262,6 @@
             underTest.onAuthenticateButtonClicked()
             assertThat(message?.text).isEqualTo(WRONG_PIN)
             assertThat(entries).hasSize(0)
-            assertThat(isUnlocked).isFalse()
             assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
 
             // Enter the correct PIN:
@@ -281,21 +273,18 @@
 
             underTest.onAuthenticateButtonClicked()
 
-            assertThat(isUnlocked).isTrue()
             assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Gone))
         }
 
     @Test
     fun onAutoConfirm_whenCorrect() =
         testScope.runTest {
-            val isUnlocked by collectLastValue(authenticationInteractor.isUnlocked)
             val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_NAME))
-            authenticationInteractor.setAuthenticationMethod(
+            utils.authenticationRepository.setAuthenticationMethod(
                 AuthenticationMethodModel.Pin(1234, autoConfirm = true)
             )
-            authenticationInteractor.lockDevice()
+            utils.authenticationRepository.setUnlocked(false)
             sceneInteractor.setCurrentScene(CONTAINER_NAME, SceneModel(SceneKey.Bouncer))
-            assertThat(isUnlocked).isFalse()
             assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
             underTest.onShown()
             underTest.onPinButtonClicked(1)
@@ -303,23 +292,20 @@
             underTest.onPinButtonClicked(3)
             underTest.onPinButtonClicked(4)
 
-            assertThat(isUnlocked).isTrue()
             assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Gone))
         }
 
     @Test
     fun onAutoConfirm_whenWrong() =
         testScope.runTest {
-            val isUnlocked by collectLastValue(authenticationInteractor.isUnlocked)
             val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_NAME))
             val message by collectLastValue(bouncerViewModel.message)
             val entries by collectLastValue(underTest.pinEntries)
-            authenticationInteractor.setAuthenticationMethod(
+            utils.authenticationRepository.setAuthenticationMethod(
                 AuthenticationMethodModel.Pin(1234, autoConfirm = true)
             )
-            authenticationInteractor.lockDevice()
+            utils.authenticationRepository.setUnlocked(false)
             sceneInteractor.setCurrentScene(CONTAINER_NAME, SceneModel(SceneKey.Bouncer))
-            assertThat(isUnlocked).isFalse()
             assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
             underTest.onShown()
             underTest.onPinButtonClicked(1)
@@ -329,7 +315,6 @@
 
             assertThat(entries).hasSize(0)
             assertThat(message?.text).isEqualTo(WRONG_PIN)
-            assertThat(isUnlocked).isFalse()
             assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
         }
 
@@ -338,7 +323,7 @@
         testScope.runTest {
             val backspaceButtonAppearance by collectLastValue(underTest.backspaceButtonAppearance)
 
-            authenticationInteractor.setAuthenticationMethod(
+            utils.authenticationRepository.setAuthenticationMethod(
                 AuthenticationMethodModel.Pin(1234, autoConfirm = false)
             )
 
@@ -349,7 +334,7 @@
     fun backspaceButtonAppearance_withAutoConfirmButNoInput_isHidden() =
         testScope.runTest {
             val backspaceButtonAppearance by collectLastValue(underTest.backspaceButtonAppearance)
-            authenticationInteractor.setAuthenticationMethod(
+            utils.authenticationRepository.setAuthenticationMethod(
                 AuthenticationMethodModel.Pin(1234, autoConfirm = true)
             )
 
@@ -360,7 +345,7 @@
     fun backspaceButtonAppearance_withAutoConfirmAndInput_isShownQuiet() =
         testScope.runTest {
             val backspaceButtonAppearance by collectLastValue(underTest.backspaceButtonAppearance)
-            authenticationInteractor.setAuthenticationMethod(
+            utils.authenticationRepository.setAuthenticationMethod(
                 AuthenticationMethodModel.Pin(1234, autoConfirm = true)
             )
 
@@ -374,7 +359,7 @@
         testScope.runTest {
             val confirmButtonAppearance by collectLastValue(underTest.confirmButtonAppearance)
 
-            authenticationInteractor.setAuthenticationMethod(
+            utils.authenticationRepository.setAuthenticationMethod(
                 AuthenticationMethodModel.Pin(1234, autoConfirm = false)
             )
 
@@ -385,7 +370,7 @@
     fun confirmButtonAppearance_withAutoConfirm_isHidden() =
         testScope.runTest {
             val confirmButtonAppearance by collectLastValue(underTest.confirmButtonAppearance)
-            authenticationInteractor.setAuthenticationMethod(
+            utils.authenticationRepository.setAuthenticationMethod(
                 AuthenticationMethodModel.Pin(1234, autoConfirm = true)
             )
 
@@ -396,7 +381,7 @@
     fun hintedPinLength_withoutAutoConfirm_isNull() =
         testScope.runTest {
             val hintedPinLength by collectLastValue(underTest.hintedPinLength)
-            authenticationInteractor.setAuthenticationMethod(
+            utils.authenticationRepository.setAuthenticationMethod(
                 AuthenticationMethodModel.Pin(1234, autoConfirm = false)
             )
 
@@ -407,7 +392,7 @@
     fun hintedPinLength_withAutoConfirmPinLessThanSixDigits_isNull() =
         testScope.runTest {
             val hintedPinLength by collectLastValue(underTest.hintedPinLength)
-            authenticationInteractor.setAuthenticationMethod(
+            utils.authenticationRepository.setAuthenticationMethod(
                 AuthenticationMethodModel.Pin(12345, autoConfirm = true)
             )
 
@@ -418,7 +403,7 @@
     fun hintedPinLength_withAutoConfirmPinExactlySixDigits_isSix() =
         testScope.runTest {
             val hintedPinLength by collectLastValue(underTest.hintedPinLength)
-            authenticationInteractor.setAuthenticationMethod(
+            utils.authenticationRepository.setAuthenticationMethod(
                 AuthenticationMethodModel.Pin(123456, autoConfirm = true)
             )
 
@@ -429,7 +414,7 @@
     fun hintedPinLength_withAutoConfirmPinMoreThanSixDigits_isNull() =
         testScope.runTest {
             val hintedPinLength by collectLastValue(underTest.hintedPinLength)
-            authenticationInteractor.setAuthenticationMethod(
+            utils.authenticationRepository.setAuthenticationMethod(
                 AuthenticationMethodModel.Pin(1234567, autoConfirm = true)
             )
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/display/data/repository/DisplayMetricsRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/display/data/repository/DisplayMetricsRepositoryTest.kt
new file mode 100644
index 0000000..dd741b4
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/display/data/repository/DisplayMetricsRepositoryTest.kt
@@ -0,0 +1,96 @@
+/*
+ * 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.display.data.repository
+
+import android.content.Context
+import android.util.DisplayMetrics
+import android.view.Display
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.statusbar.policy.FakeConfigurationController
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.whenever
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.StandardTestDispatcher
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+
+@SmallTest
+@OptIn(ExperimentalCoroutinesApi::class)
+class DisplayMetricsRepositoryTest : SysuiTestCase() {
+    private lateinit var underTest: DisplayMetricsRepository
+
+    private val testScope = TestScope(StandardTestDispatcher())
+    private val configurationController = FakeConfigurationController()
+
+    private val displayMetrics =
+        DisplayMetrics().apply { this.heightPixels = INITIAL_HEIGHT_PIXELS }
+    private val mockContext: Context = mock()
+    private val mockDisplay: Display = mock()
+
+    @Before
+    fun setUp() {
+        underTest =
+            DisplayMetricsRepository(
+                testScope.backgroundScope,
+                configurationController,
+                displayMetrics,
+                mockContext,
+                LogBuffer("TestBuffer", maxSize = 10, logcatEchoTracker = mock())
+            )
+        whenever(mockContext.display).thenReturn(mockDisplay)
+    }
+
+    @Test
+    fun heightPixels_getsInitialValue() {
+        assertThat(underTest.heightPixels).isEqualTo(INITIAL_HEIGHT_PIXELS)
+    }
+
+    @Test
+    fun heightPixels_configChanged_heightUpdated() =
+        testScope.runTest {
+            runCurrent()
+
+            updateDisplayMetrics(456)
+            configurationController.notifyConfigurationChanged()
+            runCurrent()
+
+            assertThat(underTest.heightPixels).isEqualTo(456)
+
+            updateDisplayMetrics(23)
+            configurationController.notifyConfigurationChanged()
+            runCurrent()
+
+            assertThat(underTest.heightPixels).isEqualTo(23)
+        }
+
+    private fun updateDisplayMetrics(newHeight: Int) {
+        whenever(mockDisplay.getMetrics(displayMetrics)).thenAnswer {
+            it.getArgument<DisplayMetrics>(0).heightPixels = newHeight
+            Unit
+        }
+    }
+
+    private companion object {
+        const val INITIAL_HEIGHT_PIXELS = 345
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/display/domain/interactor/ConnectedDisplayInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/display/domain/interactor/ConnectedDisplayInteractorTest.kt
new file mode 100644
index 0000000..1b597f4
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/display/domain/interactor/ConnectedDisplayInteractorTest.kt
@@ -0,0 +1,148 @@
+/*
+ * 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.display.domain.interactor
+
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper
+import android.view.Display
+import android.view.Display.TYPE_EXTERNAL
+import android.view.Display.TYPE_INTERNAL
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.FlowValue
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.display.data.repository.DisplayRepository
+import com.android.systemui.display.domain.interactor.ConnectedDisplayInteractor.State
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.whenever
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableSharedFlow
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.UnconfinedTestDispatcher
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidTestingRunner::class)
+@TestableLooper.RunWithLooper
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+class ConnectedDisplayInteractorTest : SysuiTestCase() {
+
+    private val fakeDisplayRepository = FakeDisplayRepository()
+    private val connectedDisplayStateProvider: ConnectedDisplayInteractor =
+        ConnectedDisplayInteractorImpl(fakeDisplayRepository)
+    private val testScope = TestScope(UnconfinedTestDispatcher())
+
+    @Test
+    fun displayState_nullDisplays_disconnected() =
+        testScope.runTest {
+            val value by lastValue()
+
+            fakeDisplayRepository.emit(emptySet())
+
+            assertThat(value).isEqualTo(State.DISCONNECTED)
+        }
+
+    @Test
+    fun displayState_emptyDisplays_disconnected() =
+        testScope.runTest {
+            val value by lastValue()
+
+            fakeDisplayRepository.emit(emptySet())
+
+            assertThat(value).isEqualTo(State.DISCONNECTED)
+        }
+
+    @Test
+    fun displayState_internalDisplay_disconnected() =
+        testScope.runTest {
+            val value by lastValue()
+
+            fakeDisplayRepository.emit(setOf(display(type = TYPE_INTERNAL)))
+
+            assertThat(value).isEqualTo(State.DISCONNECTED)
+        }
+
+    @Test
+    fun displayState_externalDisplay_connected() =
+        testScope.runTest {
+            val value by lastValue()
+
+            fakeDisplayRepository.emit(setOf(display(type = TYPE_EXTERNAL)))
+
+            assertThat(value).isEqualTo(State.CONNECTED)
+        }
+
+    @Test
+    fun displayState_multipleExternalDisplays_connected() =
+        testScope.runTest {
+            val value by lastValue()
+
+            fakeDisplayRepository.emit(
+                setOf(display(type = TYPE_EXTERNAL), display(type = TYPE_EXTERNAL))
+            )
+
+            assertThat(value).isEqualTo(State.CONNECTED)
+        }
+
+    @Test
+    fun displayState_externalSecure_connectedSecure() =
+        testScope.runTest {
+            val value by lastValue()
+
+            fakeDisplayRepository.emit(
+                setOf(display(type = TYPE_EXTERNAL, flags = Display.FLAG_SECURE))
+            )
+
+            assertThat(value).isEqualTo(State.CONNECTED_SECURE)
+        }
+
+    @Test
+    fun displayState_multipleExternal_onlyOneSecure_connectedSecure() =
+        testScope.runTest {
+            val value by lastValue()
+
+            fakeDisplayRepository.emit(
+                setOf(
+                    display(type = TYPE_EXTERNAL, flags = Display.FLAG_SECURE),
+                    display(type = TYPE_EXTERNAL, flags = 0)
+                )
+            )
+
+            assertThat(value).isEqualTo(State.CONNECTED_SECURE)
+        }
+
+    private fun TestScope.lastValue(): FlowValue<State?> =
+        collectLastValue(connectedDisplayStateProvider.connectedDisplayState)
+
+    private fun display(type: Int, flags: Int = 0): Display {
+        return mock<Display>().also { mockDisplay ->
+            whenever(mockDisplay.type).thenReturn(type)
+            whenever(mockDisplay.flags).thenReturn(flags)
+        }
+    }
+
+    private class FakeDisplayRepository : DisplayRepository {
+        private val flow = MutableSharedFlow<Set<Display>>()
+        suspend fun emit(value: Set<Display>) = flow.emit(value)
+        override val displays: Flow<Set<Display>>
+            get() = flow
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dump/LogBufferHelper.kt b/packages/SystemUI/tests/src/com/android/systemui/dump/LogBufferHelper.kt
index bd029a7..a341ca3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dump/LogBufferHelper.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/dump/LogBufferHelper.kt
@@ -17,7 +17,7 @@
 package com.android.systemui.dump
 
 import com.android.systemui.log.LogBuffer
-import com.android.systemui.log.LogLevel
+import com.android.systemui.log.core.LogLevel
 import com.android.systemui.log.LogcatEchoTracker
 
 /**
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
index 12a9f94..6f7c217 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
@@ -780,6 +780,11 @@
         assertTrue(mViewMediator.isShowingAndNotOccluded());
     }
 
+    @Test
+    public void testBouncerSwipeDown() {
+        mViewMediator.getViewMediatorCallback().onBouncerSwipeDown();
+        verify(mStatusBarKeyguardViewManager).reset(true);
+    }
     private void createAndStartViewMediator() {
         mViewMediator = new KeyguardViewMediator(
                 mContext,
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 5922cbf..e042564 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
@@ -260,6 +260,7 @@
             assertThat(authStatus()).isEqualTo(SuccessAuthenticationStatus(successResult))
             assertThat(authenticated()).isTrue()
             assertThat(authRunning()).isFalse()
+            assertThat(canFaceAuthRun()).isFalse()
         }
 
     private fun uiEventIsLogged(faceAuthUiEvent: FaceAuthUiEvent) {
@@ -417,13 +418,9 @@
                 FACE_ERROR_CANCELED,
                 "First auth attempt cancellation completed"
             )
-            assertThat(authStatus())
-                .isEqualTo(
-                    ErrorAuthenticationStatus(
-                        FACE_ERROR_CANCELED,
-                        "First auth attempt cancellation completed"
-                    )
-                )
+            val value = authStatus() as ErrorAuthenticationStatus
+            assertThat(value.msgId).isEqualTo(FACE_ERROR_CANCELED)
+            assertThat(value.msg).isEqualTo("First auth attempt cancellation completed")
 
             faceAuthenticateIsCalled()
             uiEventIsLogged(FACE_AUTH_TRIGGERED_ALTERNATE_BIOMETRIC_BOUNCER_SHOWN)
@@ -551,20 +548,6 @@
         }
 
     @Test
-    fun authenticateDoesNotRunWhenDeviceIsSleeping() =
-        testScope.runTest {
-            testGatingCheckForFaceAuth {
-                keyguardRepository.setWakefulnessModel(
-                    WakefulnessModel(
-                        state = WakefulnessState.ASLEEP,
-                        lastWakeReason = WakeSleepReason.OTHER,
-                        lastSleepReason = WakeSleepReason.OTHER,
-                    )
-                )
-            }
-        }
-
-    @Test
     fun authenticateDoesNotRunWhenNonStrongBiometricIsNotAllowed() =
         testScope.runTest {
             testGatingCheckForFaceAuth {
@@ -586,6 +569,29 @@
         }
 
     @Test
+    fun authenticateRunsWhenSecureCameraIsActiveIfBouncerIsShowing() =
+        testScope.runTest {
+            initCollectors()
+            allPreconditionsToRunFaceAuthAreTrue()
+            bouncerRepository.setAlternateVisible(false)
+            bouncerRepository.setPrimaryShow(false)
+
+            assertThat(canFaceAuthRun()).isTrue()
+
+            // launch secure camera
+            fakeCommandQueue.doForEachCallback {
+                it.onCameraLaunchGestureDetected(CAMERA_LAUNCH_SOURCE_POWER_DOUBLE_TAP)
+            }
+            keyguardRepository.setKeyguardOccluded(true)
+            runCurrent()
+            assertThat(canFaceAuthRun()).isFalse()
+
+            // but bouncer is shown after that.
+            bouncerRepository.setPrimaryShow(true)
+            assertThat(canFaceAuthRun()).isTrue()
+        }
+
+    @Test
     fun authenticateDoesNotRunOnUnsupportedPosture() =
         testScope.runTest {
             testGatingCheckForFaceAuth {
@@ -616,6 +622,27 @@
         }
 
     @Test
+    fun authenticateFallbacksToDetectionWhenUserIsAlreadyTrustedByTrustManager() =
+        testScope.runTest {
+            whenever(faceManager.sensorPropertiesInternal)
+                .thenReturn(listOf(createFaceSensorProperties(supportsFaceDetection = true)))
+            whenever(bypassController.bypassEnabled).thenReturn(true)
+            underTest = createDeviceEntryFaceAuthRepositoryImpl()
+            initCollectors()
+            allPreconditionsToRunFaceAuthAreTrue()
+
+            trustRepository.setCurrentUserTrusted(true)
+            assertThat(canFaceAuthRun()).isFalse()
+            underTest.authenticate(
+                FACE_AUTH_TRIGGERED_SWIPE_UP_ON_BOUNCER,
+                fallbackToDetection = true
+            )
+            faceAuthenticateIsNotCalled()
+
+            faceDetectIsCalled()
+        }
+
+    @Test
     fun everythingWorksWithFaceAuthRefactorFlagDisabled() =
         testScope.runTest {
             featureFlags.set(FACE_AUTH_REFACTOR, false)
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 953d618..25573de 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
@@ -235,11 +235,10 @@
     fun isKeyguardUnlocked() =
         testScope.runTest {
             whenever(keyguardStateController.isUnlocked).thenReturn(false)
-            var latest: Boolean? = null
-            val job = underTest.isKeyguardUnlocked.onEach { latest = it }.launchIn(this)
+            val isKeyguardUnlocked by collectLastValue(underTest.isKeyguardUnlocked)
 
             runCurrent()
-            assertThat(latest).isFalse()
+            assertThat(isKeyguardUnlocked).isFalse()
 
             val captor = argumentCaptor<KeyguardStateController.Callback>()
             verify(keyguardStateController).addCallback(captor.capture())
@@ -247,14 +246,12 @@
             whenever(keyguardStateController.isUnlocked).thenReturn(true)
             captor.value.onUnlockedChanged()
             runCurrent()
-            assertThat(latest).isTrue()
+            assertThat(isKeyguardUnlocked).isTrue()
 
             whenever(keyguardStateController.isUnlocked).thenReturn(false)
-            captor.value.onUnlockedChanged()
+            captor.value.onKeyguardShowingChanged()
             runCurrent()
-            assertThat(latest).isFalse()
-
-            job.cancel()
+            assertThat(isKeyguardUnlocked).isFalse()
         }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardFaceAuthInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardFaceAuthInteractorTest.kt
index 80700e5..ee5c1cc3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardFaceAuthInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardFaceAuthInteractorTest.kt
@@ -17,6 +17,7 @@
 
 package com.android.systemui.keyguard.domain.interactor
 
+import android.hardware.biometrics.BiometricFaceConstants
 import android.os.Handler
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
@@ -30,6 +31,7 @@
 import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor
 import com.android.systemui.bouncer.ui.BouncerView
 import com.android.systemui.classifier.FalsingCollector
+import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.dump.logcatLogBuffer
 import com.android.systemui.flags.FakeFeatureFlags
 import com.android.systemui.flags.Flags
@@ -39,6 +41,7 @@
 import com.android.systemui.keyguard.data.repository.FakeDeviceEntryFingerprintAuthRepository
 import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
 import com.android.systemui.keyguard.data.repository.FakeTrustRepository
+import com.android.systemui.keyguard.shared.model.ErrorAuthenticationStatus
 import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.keyguard.shared.model.TransitionState
 import com.android.systemui.keyguard.shared.model.TransitionStep
@@ -90,6 +93,7 @@
 
         underTest =
             SystemUIKeyguardFaceAuthInteractor(
+                mContext,
                 testScope.backgroundScope,
                 dispatcher,
                 faceAuthRepository,
@@ -144,6 +148,22 @@
         }
 
     @Test
+    fun whenFaceIsLockedOutAnyAttemptsToTriggerFaceAuthMustProvideLockoutError() =
+        testScope.runTest {
+            underTest.start()
+            val authenticationStatus = collectLastValue(underTest.authenticationStatus)
+            faceAuthRepository.setLockedOut(true)
+
+            underTest.onDeviceLifted()
+
+            val outputValue = authenticationStatus()!! as ErrorAuthenticationStatus
+            assertThat(outputValue.msgId)
+                .isEqualTo(BiometricFaceConstants.FACE_ERROR_LOCKOUT_PERMANENT)
+            assertThat(outputValue.msg).isEqualTo("Face Unlock unavailable")
+            assertThat(faceAuthRepository.runningAuthRequest.value).isNull()
+        }
+
+    @Test
     fun faceAuthIsRequestedWhenLockscreenBecomesVisibleFromAodState() =
         testScope.runTest {
             underTest.start()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt
index 50075b5..b559015 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt
@@ -25,7 +25,6 @@
 import com.android.systemui.flags.Flags
 import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
 import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
-import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
 import com.android.systemui.keyguard.shared.model.BiometricUnlockModel
 import com.android.systemui.keyguard.shared.model.DozeStateModel
 import com.android.systemui.keyguard.shared.model.DozeTransitionModel
@@ -56,6 +55,7 @@
 import org.mockito.Mock
 import org.mockito.Mockito.never
 import org.mockito.Mockito.reset
+import org.mockito.Mockito.spy
 import org.mockito.Mockito.verify
 import org.mockito.MockitoAnnotations
 
@@ -72,10 +72,10 @@
     private lateinit var bouncerRepository: FakeKeyguardBouncerRepository
     private lateinit var shadeRepository: ShadeRepository
     private lateinit var transitionRepository: FakeKeyguardTransitionRepository
+    private lateinit var transitionInteractor: KeyguardTransitionInteractor
     private lateinit var featureFlags: FakeFeatureFlags
 
     // Used to verify transition requests for test output
-    @Mock private lateinit var mockTransitionRepository: KeyguardTransitionRepository
     @Mock private lateinit var keyguardSecurityModel: KeyguardSecurityModel
 
     private lateinit var fromLockscreenTransitionInteractor: FromLockscreenTransitionInteractor
@@ -97,92 +97,71 @@
         keyguardRepository = FakeKeyguardRepository()
         bouncerRepository = FakeKeyguardBouncerRepository()
         shadeRepository = FakeShadeRepository()
-        transitionRepository = FakeKeyguardTransitionRepository()
+        transitionRepository = spy(FakeKeyguardTransitionRepository())
+        transitionInteractor = KeyguardTransitionInteractor(
+                transitionRepository, testScope.backgroundScope)
 
         whenever(keyguardSecurityModel.getSecurityMode(anyInt())).thenReturn(PIN)
 
         featureFlags = FakeFeatureFlags().apply { set(Flags.FACE_AUTH_REFACTOR, true) }
-        fromLockscreenTransitionInteractor =
-            FromLockscreenTransitionInteractor(
+
+        fromLockscreenTransitionInteractor = FromLockscreenTransitionInteractor(
                 scope = testScope,
                 keyguardInteractor = createKeyguardInteractor(),
+                transitionRepository = transitionRepository,
+                transitionInteractor = transitionInteractor,
                 shadeRepository = shadeRepository,
-                keyguardTransitionRepository = mockTransitionRepository,
-                keyguardTransitionInteractor =
-                    KeyguardTransitionInteractor(transitionRepository, testScope.backgroundScope),
-            )
-        fromLockscreenTransitionInteractor.start()
+        ).apply { start() }
 
-        fromDreamingTransitionInteractor =
-            FromDreamingTransitionInteractor(
+        fromPrimaryBouncerTransitionInteractor = FromPrimaryBouncerTransitionInteractor(
                 scope = testScope,
                 keyguardInteractor = createKeyguardInteractor(),
-                keyguardTransitionRepository = mockTransitionRepository,
-                keyguardTransitionInteractor =
-                    KeyguardTransitionInteractor(transitionRepository, testScope.backgroundScope),
-            )
-        fromDreamingTransitionInteractor.start()
-
-        fromAodTransitionInteractor =
-            FromAodTransitionInteractor(
-                scope = testScope,
-                keyguardInteractor = createKeyguardInteractor(),
-                keyguardTransitionRepository = mockTransitionRepository,
-                keyguardTransitionInteractor =
-                    KeyguardTransitionInteractor(transitionRepository, testScope.backgroundScope),
-            )
-        fromAodTransitionInteractor.start()
-
-        fromGoneTransitionInteractor =
-            FromGoneTransitionInteractor(
-                scope = testScope,
-                keyguardInteractor = createKeyguardInteractor(),
-                keyguardTransitionRepository = mockTransitionRepository,
-                keyguardTransitionInteractor =
-                    KeyguardTransitionInteractor(transitionRepository, testScope.backgroundScope),
-            )
-        fromGoneTransitionInteractor.start()
-
-        fromDozingTransitionInteractor =
-            FromDozingTransitionInteractor(
-                scope = testScope,
-                keyguardInteractor = createKeyguardInteractor(),
-                keyguardTransitionRepository = mockTransitionRepository,
-                keyguardTransitionInteractor =
-                    KeyguardTransitionInteractor(transitionRepository, testScope.backgroundScope),
-            )
-        fromDozingTransitionInteractor.start()
-
-        fromOccludedTransitionInteractor =
-            FromOccludedTransitionInteractor(
-                scope = testScope,
-                keyguardInteractor = createKeyguardInteractor(),
-                keyguardTransitionRepository = mockTransitionRepository,
-                keyguardTransitionInteractor =
-                    KeyguardTransitionInteractor(transitionRepository, testScope.backgroundScope),
-            )
-        fromOccludedTransitionInteractor.start()
-
-        fromAlternateBouncerTransitionInteractor =
-            FromAlternateBouncerTransitionInteractor(
-                scope = testScope,
-                keyguardInteractor = createKeyguardInteractor(),
-                keyguardTransitionRepository = mockTransitionRepository,
-                keyguardTransitionInteractor =
-                    KeyguardTransitionInteractor(transitionRepository, testScope.backgroundScope),
-            )
-        fromAlternateBouncerTransitionInteractor.start()
-
-        fromPrimaryBouncerTransitionInteractor =
-            FromPrimaryBouncerTransitionInteractor(
-                scope = testScope,
-                keyguardInteractor = createKeyguardInteractor(),
-                keyguardTransitionRepository = mockTransitionRepository,
-                keyguardTransitionInteractor =
-                    KeyguardTransitionInteractor(transitionRepository, testScope.backgroundScope),
+                transitionRepository = transitionRepository,
+                transitionInteractor = transitionInteractor,
                 keyguardSecurityModel = keyguardSecurityModel,
-            )
-        fromPrimaryBouncerTransitionInteractor.start()
+        ).apply { start() }
+
+        fromDreamingTransitionInteractor = FromDreamingTransitionInteractor(
+                scope = testScope,
+                keyguardInteractor = createKeyguardInteractor(),
+                transitionRepository = transitionRepository,
+                transitionInteractor = transitionInteractor,
+        ).apply { start() }
+
+        fromAodTransitionInteractor = FromAodTransitionInteractor(
+                scope = testScope,
+                keyguardInteractor = createKeyguardInteractor(),
+                transitionRepository = transitionRepository,
+                transitionInteractor = transitionInteractor,
+        ).apply { start() }
+
+        fromGoneTransitionInteractor = FromGoneTransitionInteractor(
+                scope = testScope,
+                keyguardInteractor = createKeyguardInteractor(),
+                transitionRepository = transitionRepository,
+                transitionInteractor = transitionInteractor,
+        ).apply { start() }
+
+        fromDozingTransitionInteractor = FromDozingTransitionInteractor(
+                scope = testScope,
+                keyguardInteractor = createKeyguardInteractor(),
+                transitionRepository = transitionRepository,
+                transitionInteractor = transitionInteractor,
+        ).apply { start() }
+
+        fromOccludedTransitionInteractor = FromOccludedTransitionInteractor(
+                scope = testScope,
+                keyguardInteractor = createKeyguardInteractor(),
+                transitionRepository = transitionRepository,
+                transitionInteractor = transitionInteractor,
+        ).apply { start() }
+
+        fromAlternateBouncerTransitionInteractor = FromAlternateBouncerTransitionInteractor(
+                scope = testScope,
+                keyguardInteractor = createKeyguardInteractor(),
+                transitionRepository = transitionRepository,
+                transitionInteractor = transitionInteractor,
+        ).apply { start() }
     }
 
     @Test
@@ -201,7 +180,7 @@
 
             val info =
                 withArgCaptor<TransitionInfo> {
-                    verify(mockTransitionRepository).startTransition(capture(), anyBoolean())
+                    verify(transitionRepository).startTransition(capture(), anyBoolean())
                 }
             // THEN a transition to PRIMARY_BOUNCER should occur
             assertThat(info.ownerName).isEqualTo("FromLockscreenTransitionInteractor")
@@ -228,7 +207,7 @@
 
             val info =
                 withArgCaptor<TransitionInfo> {
-                    verify(mockTransitionRepository).startTransition(capture(), anyBoolean())
+                    verify(transitionRepository).startTransition(capture(), anyBoolean())
                 }
             // THEN a transition to DOZING should occur
             assertThat(info.ownerName).isEqualTo("FromOccludedTransitionInteractor")
@@ -255,7 +234,7 @@
 
             val info =
                 withArgCaptor<TransitionInfo> {
-                    verify(mockTransitionRepository).startTransition(capture(), anyBoolean())
+                    verify(transitionRepository).startTransition(capture(), anyBoolean())
                 }
             // THEN a transition to DOZING should occur
             assertThat(info.ownerName).isEqualTo("FromOccludedTransitionInteractor")
@@ -286,7 +265,7 @@
 
             val info =
                 withArgCaptor<TransitionInfo> {
-                    verify(mockTransitionRepository).startTransition(capture(), anyBoolean())
+                    verify(transitionRepository).startTransition(capture(), anyBoolean())
                 }
             // THEN a transition to DREAMING should occur
             assertThat(info.ownerName).isEqualTo("FromLockscreenTransitionInteractor")
@@ -313,7 +292,7 @@
 
             val info =
                 withArgCaptor<TransitionInfo> {
-                    verify(mockTransitionRepository).startTransition(capture(), anyBoolean())
+                    verify(transitionRepository).startTransition(capture(), anyBoolean())
                 }
             // THEN a transition to DOZING should occur
             assertThat(info.ownerName).isEqualTo("FromLockscreenTransitionInteractor")
@@ -340,7 +319,7 @@
 
             val info =
                 withArgCaptor<TransitionInfo> {
-                    verify(mockTransitionRepository).startTransition(capture(), anyBoolean())
+                    verify(transitionRepository).startTransition(capture(), anyBoolean())
                 }
             // THEN a transition to DOZING should occur
             assertThat(info.ownerName).isEqualTo("FromLockscreenTransitionInteractor")
@@ -363,7 +342,7 @@
 
             val info =
                 withArgCaptor<TransitionInfo> {
-                    verify(mockTransitionRepository).startTransition(capture(), anyBoolean())
+                    verify(transitionRepository).startTransition(capture(), anyBoolean())
                 }
             // THEN a transition to DOZING should occur
             assertThat(info.ownerName).isEqualTo("FromDozingTransitionInteractor")
@@ -388,14 +367,14 @@
                 )
             )
             runCurrent()
-            reset(mockTransitionRepository)
+            reset(transitionRepository)
 
             // WHEN a signal comes that dreaming is enabled
             keyguardRepository.setDreamingWithOverlay(true)
             advanceUntilIdle()
 
             // THEN the transition is ignored
-            verify(mockTransitionRepository, never()).startTransition(any(), anyBoolean())
+            verify(transitionRepository, never()).startTransition(any(), anyBoolean())
 
             coroutineContext.cancelChildren()
         }
@@ -412,7 +391,7 @@
 
             val info =
                 withArgCaptor<TransitionInfo> {
-                    verify(mockTransitionRepository).startTransition(capture(), anyBoolean())
+                    verify(transitionRepository).startTransition(capture(), anyBoolean())
                 }
             // THEN a transition to DOZING should occur
             assertThat(info.ownerName).isEqualTo("FromDozingTransitionInteractor")
@@ -439,7 +418,7 @@
 
             val info =
                 withArgCaptor<TransitionInfo> {
-                    verify(mockTransitionRepository).startTransition(capture(), anyBoolean())
+                    verify(transitionRepository).startTransition(capture(), anyBoolean())
                 }
             // THEN a transition to DOZING should occur
             assertThat(info.ownerName).isEqualTo("FromGoneTransitionInteractor")
@@ -466,7 +445,7 @@
 
             val info =
                 withArgCaptor<TransitionInfo> {
-                    verify(mockTransitionRepository).startTransition(capture(), anyBoolean())
+                    verify(transitionRepository).startTransition(capture(), anyBoolean())
                 }
             // THEN a transition to AOD should occur
             assertThat(info.ownerName).isEqualTo("FromGoneTransitionInteractor")
@@ -489,7 +468,7 @@
 
             val info =
                 withArgCaptor<TransitionInfo> {
-                    verify(mockTransitionRepository).startTransition(capture(), anyBoolean())
+                    verify(transitionRepository).startTransition(capture(), anyBoolean())
                 }
             // THEN a transition to AOD should occur
             assertThat(info.ownerName).isEqualTo("FromGoneTransitionInteractor")
@@ -520,7 +499,7 @@
 
             val info =
                 withArgCaptor<TransitionInfo> {
-                    verify(mockTransitionRepository).startTransition(capture(), anyBoolean())
+                    verify(transitionRepository).startTransition(capture(), anyBoolean())
                 }
             // THEN a transition to DREAMING should occur
             assertThat(info.ownerName).isEqualTo("FromGoneTransitionInteractor")
@@ -543,7 +522,7 @@
 
             val info =
                 withArgCaptor<TransitionInfo> {
-                    verify(mockTransitionRepository).startTransition(capture(), anyBoolean())
+                    verify(transitionRepository).startTransition(capture(), anyBoolean())
                 }
             // THEN a transition to PRIMARY_BOUNCER should occur
             assertThat(info.ownerName).isEqualTo("FromAlternateBouncerTransitionInteractor")
@@ -572,7 +551,7 @@
 
             val info =
                 withArgCaptor<TransitionInfo> {
-                    verify(mockTransitionRepository).startTransition(capture(), anyBoolean())
+                    verify(transitionRepository).startTransition(capture(), anyBoolean())
                 }
             // THEN a transition to AOD should occur
             assertThat(info.ownerName).isEqualTo("FromAlternateBouncerTransitionInteractor")
@@ -602,7 +581,7 @@
 
             val info =
                 withArgCaptor<TransitionInfo> {
-                    verify(mockTransitionRepository).startTransition(capture(), anyBoolean())
+                    verify(transitionRepository).startTransition(capture(), anyBoolean())
                 }
             // THEN a transition to DOZING should occur
             assertThat(info.ownerName).isEqualTo("FromAlternateBouncerTransitionInteractor")
@@ -630,7 +609,7 @@
 
             val info =
                 withArgCaptor<TransitionInfo> {
-                    verify(mockTransitionRepository).startTransition(capture(), anyBoolean())
+                    verify(transitionRepository).startTransition(capture(), anyBoolean())
                 }
             // THEN a transition to LOCKSCREEN should occur
             assertThat(info.ownerName).isEqualTo("FromAlternateBouncerTransitionInteractor")
@@ -658,7 +637,7 @@
 
             val info =
                 withArgCaptor<TransitionInfo> {
-                    verify(mockTransitionRepository).startTransition(capture(), anyBoolean())
+                    verify(transitionRepository).startTransition(capture(), anyBoolean())
                 }
             // THEN a transition to AOD should occur
             assertThat(info.ownerName).isEqualTo("FromPrimaryBouncerTransitionInteractor")
@@ -686,7 +665,7 @@
 
             val info =
                 withArgCaptor<TransitionInfo> {
-                    verify(mockTransitionRepository).startTransition(capture(), anyBoolean())
+                    verify(transitionRepository).startTransition(capture(), anyBoolean())
                 }
             // THEN a transition to DOZING should occur
             assertThat(info.ownerName).isEqualTo("FromPrimaryBouncerTransitionInteractor")
@@ -713,7 +692,7 @@
 
             val info =
                 withArgCaptor<TransitionInfo> {
-                    verify(mockTransitionRepository).startTransition(capture(), anyBoolean())
+                    verify(transitionRepository).startTransition(capture(), anyBoolean())
                 }
             // THEN a transition to LOCKSCREEN should occur
             assertThat(info.ownerName).isEqualTo("FromPrimaryBouncerTransitionInteractor")
@@ -744,7 +723,7 @@
 
             val info =
                 withArgCaptor<TransitionInfo> {
-                    verify(mockTransitionRepository).startTransition(capture(), anyBoolean())
+                    verify(transitionRepository).startTransition(capture(), anyBoolean())
                 }
             // THEN a transition to GONE should occur
             assertThat(info.ownerName).isEqualTo("FromOccludedTransitionInteractor")
@@ -773,7 +752,7 @@
 
             val info =
                 withArgCaptor<TransitionInfo> {
-                    verify(mockTransitionRepository).startTransition(capture(), anyBoolean())
+                    verify(transitionRepository).startTransition(capture(), anyBoolean())
                 }
             // THEN a transition to LOCKSCREEN should occur
             assertThat(info.ownerName).isEqualTo("FromOccludedTransitionInteractor")
@@ -799,7 +778,7 @@
 
             val info =
                 withArgCaptor<TransitionInfo> {
-                    verify(mockTransitionRepository).startTransition(capture(), anyBoolean())
+                    verify(transitionRepository).startTransition(capture(), anyBoolean())
                 }
             // THEN a transition to AlternateBouncer should occur
             assertThat(info.ownerName).isEqualTo("FromOccludedTransitionInteractor")
@@ -828,7 +807,7 @@
 
             val info =
                 withArgCaptor<TransitionInfo> {
-                    verify(mockTransitionRepository).startTransition(capture(), anyBoolean())
+                    verify(transitionRepository).startTransition(capture(), anyBoolean())
                 }
             // THEN a transition to AlternateBouncer should occur
             assertThat(info.ownerName).isEqualTo("FromPrimaryBouncerTransitionInteractor")
@@ -890,6 +869,6 @@
             )
         )
         runCurrent()
-        reset(mockTransitionRepository)
+        reset(transitionRepository)
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/LockscreenSceneInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/LockscreenSceneInteractorTest.kt
index 65781c4..abbdc3d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/LockscreenSceneInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/LockscreenSceneInteractorTest.kt
@@ -26,7 +26,6 @@
 import com.android.systemui.scene.shared.model.SceneModel
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.test.TestScope
 import kotlinx.coroutines.test.runCurrent
 import kotlinx.coroutines.test.runTest
 import org.junit.Test
@@ -38,8 +37,8 @@
 @RunWith(JUnit4::class)
 class LockscreenSceneInteractorTest : SysuiTestCase() {
 
-    private val testScope = TestScope()
-    private val utils = SceneTestUtils(this, testScope)
+    private val utils = SceneTestUtils(this)
+    private val testScope = utils.testScope
     private val sceneInteractor = utils.sceneInteractor()
     private val authenticationInteractor =
         utils.authenticationInteractor(
@@ -61,10 +60,10 @@
         testScope.runTest {
             val isDeviceLocked by collectLastValue(underTest.isDeviceLocked)
 
-            authenticationInteractor.lockDevice()
+            utils.authenticationRepository.setUnlocked(false)
             assertThat(isDeviceLocked).isTrue()
 
-            authenticationInteractor.unlockDevice()
+            utils.authenticationRepository.setUnlocked(true)
             assertThat(isDeviceLocked).isFalse()
         }
 
@@ -73,8 +72,8 @@
         testScope.runTest {
             val isSwipeToDismissEnabled by collectLastValue(underTest.isSwipeToDismissEnabled)
 
-            authenticationInteractor.lockDevice()
-            authenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.Swipe)
+            utils.authenticationRepository.setUnlocked(false)
+            utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Swipe)
 
             assertThat(isSwipeToDismissEnabled).isTrue()
         }
@@ -84,8 +83,8 @@
         testScope.runTest {
             val isSwipeToDismissEnabled by collectLastValue(underTest.isSwipeToDismissEnabled)
 
-            authenticationInteractor.unlockDevice()
-            authenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.Swipe)
+            utils.authenticationRepository.setUnlocked(true)
+            utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Swipe)
 
             assertThat(isSwipeToDismissEnabled).isFalse()
         }
@@ -94,8 +93,10 @@
     fun dismissLockScreen_deviceLockedWithSecureAuthMethod_switchesToBouncer() =
         testScope.runTest {
             val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_1))
-            authenticationInteractor.lockDevice()
-            authenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.Pin(1234))
+            utils.authenticationRepository.setUnlocked(false)
+            utils.authenticationRepository.setAuthenticationMethod(
+                AuthenticationMethodModel.Pin(1234)
+            )
             assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Lockscreen))
 
             underTest.dismissLockscreen()
@@ -107,8 +108,10 @@
     fun dismissLockScreen_deviceUnlocked_switchesToGone() =
         testScope.runTest {
             val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_1))
-            authenticationInteractor.unlockDevice()
-            authenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.Pin(1234))
+            utils.authenticationRepository.setUnlocked(true)
+            utils.authenticationRepository.setAuthenticationMethod(
+                AuthenticationMethodModel.Pin(1234)
+            )
             assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Lockscreen))
 
             underTest.dismissLockscreen()
@@ -120,8 +123,8 @@
     fun dismissLockScreen_deviceLockedWithInsecureAuthMethod_switchesToGone() =
         testScope.runTest {
             val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_1))
-            authenticationInteractor.lockDevice()
-            authenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.Swipe)
+            utils.authenticationRepository.setUnlocked(false)
+            utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Swipe)
             assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Lockscreen))
 
             underTest.dismissLockscreen()
@@ -136,66 +139,23 @@
             runCurrent()
             sceneInteractor.setCurrentScene(CONTAINER_1, SceneModel(SceneKey.Gone))
             runCurrent()
-            authenticationInteractor.unlockDevice()
+            utils.authenticationRepository.setUnlocked(true)
             runCurrent()
             assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Gone))
 
-            authenticationInteractor.lockDevice()
+            utils.authenticationRepository.setUnlocked(false)
 
             assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Lockscreen))
         }
 
     @Test
-    fun deviceBiometricUnlockedInLockScreen_bypassEnabled_switchesToGone() =
-        testScope.runTest {
-            val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_1))
-            authenticationInteractor.lockDevice()
-            sceneInteractor.setCurrentScene(CONTAINER_1, SceneModel(SceneKey.Lockscreen))
-            if (!authenticationInteractor.isBypassEnabled.value) {
-                authenticationInteractor.toggleBypassEnabled()
-            }
-            assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Lockscreen))
-
-            authenticationInteractor.biometricUnlock()
-
-            assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Gone))
-        }
-
-    @Test
-    fun deviceBiometricUnlockedInLockScreen_bypassNotEnabled_doesNotSwitch() =
-        testScope.runTest {
-            val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_1))
-            authenticationInteractor.lockDevice()
-            sceneInteractor.setCurrentScene(CONTAINER_1, SceneModel(SceneKey.Lockscreen))
-            if (authenticationInteractor.isBypassEnabled.value) {
-                authenticationInteractor.toggleBypassEnabled()
-            }
-            assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Lockscreen))
-
-            authenticationInteractor.biometricUnlock()
-
-            assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Lockscreen))
-        }
-
-    @Test
-    fun switchFromLockScreenToGone_authMethodSwipe_unlocksDevice() =
-        testScope.runTest {
-            val isUnlocked by collectLastValue(authenticationInteractor.isUnlocked)
-            sceneInteractor.setCurrentScene(CONTAINER_1, SceneModel(SceneKey.Lockscreen))
-            authenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.Swipe)
-            assertThat(isUnlocked).isFalse()
-
-            sceneInteractor.setCurrentScene(CONTAINER_1, SceneModel(SceneKey.Gone))
-
-            assertThat(isUnlocked).isTrue()
-        }
-
-    @Test
     fun switchFromLockScreenToGone_authMethodNotSwipe_doesNotUnlockDevice() =
         testScope.runTest {
             val isUnlocked by collectLastValue(authenticationInteractor.isUnlocked)
             sceneInteractor.setCurrentScene(CONTAINER_1, SceneModel(SceneKey.Lockscreen))
-            authenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.Pin(1234))
+            utils.authenticationRepository.setAuthenticationMethod(
+                AuthenticationMethodModel.Pin(1234)
+            )
             assertThat(isUnlocked).isFalse()
 
             sceneInteractor.setCurrentScene(CONTAINER_1, SceneModel(SceneKey.Gone))
@@ -210,7 +170,7 @@
             runCurrent()
             sceneInteractor.setCurrentScene(CONTAINER_1, SceneModel(SceneKey.Shade))
             runCurrent()
-            authenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.Swipe)
+            utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Swipe)
             runCurrent()
             assertThat(isUnlocked).isFalse()
 
@@ -220,29 +180,16 @@
         }
 
     @Test
-    fun authMethodChangedToNone_onLockScreenScene_dismissesLockScreen() =
-        testScope.runTest {
-            val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_1))
-            sceneInteractor.setCurrentScene(CONTAINER_1, SceneModel(SceneKey.Lockscreen))
-            authenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.Swipe)
-            assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Lockscreen))
-
-            authenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.None)
-
-            assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Gone))
-        }
-
-    @Test
     fun authMethodChangedToNone_notOnLockScreenScene_doesNotDismissLockScreen() =
         testScope.runTest {
             val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_1))
-            authenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.Swipe)
+            utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Swipe)
             runCurrent()
             sceneInteractor.setCurrentScene(CONTAINER_1, SceneModel(SceneKey.QuickSettings))
             runCurrent()
             assertThat(currentScene).isEqualTo(SceneModel(SceneKey.QuickSettings))
 
-            authenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.None)
+            utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.None)
 
             assertThat(currentScene).isEqualTo(SceneModel(SceneKey.QuickSettings))
         }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModelTest.kt
index f0ea007..ff4ec4b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModelTest.kt
@@ -29,7 +29,6 @@
 import com.android.systemui.scene.shared.model.SceneModel
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.test.TestScope
 import kotlinx.coroutines.test.runCurrent
 import kotlinx.coroutines.test.runTest
 import org.junit.Test
@@ -41,8 +40,8 @@
 @RunWith(JUnit4::class)
 class LockscreenSceneViewModelTest : SysuiTestCase() {
 
-    private val testScope = TestScope()
-    private val utils = SceneTestUtils(this, testScope)
+    private val utils = SceneTestUtils(this)
+    private val testScope = utils.testScope
     private val sceneInteractor = utils.sceneInteractor()
     private val authenticationInteractor =
         utils.authenticationInteractor(
@@ -73,10 +72,10 @@
     fun lockButtonIcon_whenLocked() =
         testScope.runTest {
             val lockButtonIcon by collectLastValue(underTest.lockButtonIcon)
-            authenticationInteractor.setAuthenticationMethod(
+            utils.authenticationRepository.setAuthenticationMethod(
                 AuthenticationMethodModel.Password("password")
             )
-            authenticationInteractor.lockDevice()
+            utils.authenticationRepository.setUnlocked(false)
 
             assertThat((lockButtonIcon as? Icon.Resource)?.res)
                 .isEqualTo(R.drawable.ic_device_lock_on)
@@ -86,10 +85,10 @@
     fun lockButtonIcon_whenUnlocked() =
         testScope.runTest {
             val lockButtonIcon by collectLastValue(underTest.lockButtonIcon)
-            authenticationInteractor.setAuthenticationMethod(
+            utils.authenticationRepository.setAuthenticationMethod(
                 AuthenticationMethodModel.Password("password")
             )
-            authenticationInteractor.unlockDevice()
+            utils.authenticationRepository.setUnlocked(true)
 
             assertThat((lockButtonIcon as? Icon.Resource)?.res)
                 .isEqualTo(R.drawable.ic_device_lock_off)
@@ -99,8 +98,8 @@
     fun upTransitionSceneKey_swipeToUnlockedEnabled_gone() =
         testScope.runTest {
             val upTransitionSceneKey by collectLastValue(underTest.upDestinationSceneKey)
-            authenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.Swipe)
-            authenticationInteractor.lockDevice()
+            utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Swipe)
+            utils.authenticationRepository.setUnlocked(false)
 
             assertThat(upTransitionSceneKey).isEqualTo(SceneKey.Gone)
         }
@@ -109,8 +108,10 @@
     fun upTransitionSceneKey_swipeToUnlockedNotEnabled_bouncer() =
         testScope.runTest {
             val upTransitionSceneKey by collectLastValue(underTest.upDestinationSceneKey)
-            authenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.Pin(1234))
-            authenticationInteractor.lockDevice()
+            utils.authenticationRepository.setAuthenticationMethod(
+                AuthenticationMethodModel.Pin(1234)
+            )
+            utils.authenticationRepository.setUnlocked(false)
 
             assertThat(upTransitionSceneKey).isEqualTo(SceneKey.Bouncer)
         }
@@ -119,8 +120,10 @@
     fun onLockButtonClicked_deviceLockedSecurely_switchesToBouncer() =
         testScope.runTest {
             val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_1))
-            authenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.Pin(1234))
-            authenticationInteractor.lockDevice()
+            utils.authenticationRepository.setAuthenticationMethod(
+                AuthenticationMethodModel.Pin(1234)
+            )
+            utils.authenticationRepository.setUnlocked(false)
             runCurrent()
 
             underTest.onLockButtonClicked()
@@ -132,8 +135,10 @@
     fun onContentClicked_deviceUnlocked_switchesToGone() =
         testScope.runTest {
             val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_1))
-            authenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.Pin(1234))
-            authenticationInteractor.unlockDevice()
+            utils.authenticationRepository.setAuthenticationMethod(
+                AuthenticationMethodModel.Pin(1234)
+            )
+            utils.authenticationRepository.setUnlocked(true)
             runCurrent()
 
             underTest.onContentClicked()
@@ -145,8 +150,10 @@
     fun onContentClicked_deviceLockedSecurely_switchesToBouncer() =
         testScope.runTest {
             val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_1))
-            authenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.Pin(1234))
-            authenticationInteractor.lockDevice()
+            utils.authenticationRepository.setAuthenticationMethod(
+                AuthenticationMethodModel.Pin(1234)
+            )
+            utils.authenticationRepository.setUnlocked(false)
             runCurrent()
 
             underTest.onContentClicked()
@@ -158,8 +165,10 @@
     fun onLockButtonClicked_deviceUnlocked_switchesToGone() =
         testScope.runTest {
             val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_1))
-            authenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.Pin(1234))
-            authenticationInteractor.unlockDevice()
+            utils.authenticationRepository.setAuthenticationMethod(
+                AuthenticationMethodModel.Pin(1234)
+            )
+            utils.authenticationRepository.setUnlocked(true)
             runCurrent()
 
             underTest.onLockButtonClicked()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/log/LogBufferTest.kt b/packages/SystemUI/tests/src/com/android/systemui/log/LogBufferTest.kt
index 0cf6d3d..6836733 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/log/LogBufferTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/log/LogBufferTest.kt
@@ -2,6 +2,7 @@
 
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.log.core.LogLevel
 import com.google.common.truth.Truth.assertThat
 import java.io.PrintWriter
 import java.io.StringWriter
diff --git a/packages/SystemUI/tests/src/com/android/systemui/log/table/TableLogBufferTest.kt b/packages/SystemUI/tests/src/com/android/systemui/log/table/TableLogBufferTest.kt
index 12f4689..83182c5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/log/table/TableLogBufferTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/log/table/TableLogBufferTest.kt
@@ -18,8 +18,8 @@
 
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
-import com.android.systemui.log.LogLevel
 import com.android.systemui.log.LogcatEchoTracker
+import com.android.systemui.log.core.LogLevel
 import com.android.systemui.log.table.TableChange.Companion.IS_INITIAL_PREFIX
 import com.android.systemui.log.table.TableChange.Companion.MAX_STRING_LENGTH
 import com.android.systemui.util.mockito.any
diff --git a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTest.java b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTest.java
index 697d1a3..25d494c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTest.java
@@ -20,6 +20,7 @@
 import static android.app.StatusBarManager.NAVIGATION_HINT_IME_SHOWN;
 import static android.app.StatusBarManager.NAVIGATION_HINT_IME_SWITCHER_SHOWN;
 import static android.inputmethodservice.InputMethodService.BACK_DISPOSITION_DEFAULT;
+import static android.inputmethodservice.InputMethodService.IME_INVISIBLE;
 import static android.inputmethodservice.InputMethodService.IME_VISIBLE;
 import static android.view.Display.DEFAULT_DISPLAY;
 import static android.view.DisplayAdjustments.DEFAULT_DISPLAY_ADJUSTMENTS;
@@ -363,7 +364,7 @@
         externalNavBar.setImeWindowStatus(EXTERNAL_DISPLAY_ID, null, IME_VISIBLE,
                 BACK_DISPOSITION_DEFAULT, true);
         defaultNavBar.setImeWindowStatus(
-                DEFAULT_DISPLAY, null, 0 /* vis */, BACK_DISPOSITION_DEFAULT, false);
+                DEFAULT_DISPLAY, null, IME_INVISIBLE, BACK_DISPOSITION_DEFAULT, false);
         // Verify IME window state will be updated in external NavBar & default NavBar state reset.
         assertEquals(NAVIGATION_HINT_BACK_ALT | NAVIGATION_HINT_IME_SHOWN
                         | NAVIGATION_HINT_IME_SWITCHER_SHOWN,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelTest.kt
index a60dad4..fe6c9b3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelTest.kt
@@ -15,9 +15,11 @@
 
 import android.graphics.Rect
 import android.testing.AndroidTestingRunner
+import android.testing.TestableContext
 import android.testing.TestableLooper
 import android.testing.TestableLooper.RunWithLooper
 import android.testing.ViewUtils
+import android.view.ContextThemeWrapper
 import android.view.View
 import android.view.ViewGroup.LayoutParams.MATCH_PARENT
 import android.view.accessibility.AccessibilityNodeInfo
@@ -55,19 +57,24 @@
 
     private lateinit var footer: View
 
+    private val themedContext = TestableContext(
+            ContextThemeWrapper(context, R.style.Theme_SystemUI_QuickSettings)
+    )
+
     @Before
     @Throws(Exception::class)
     fun setup() {
         MockitoAnnotations.initMocks(this)
         testableLooper = TestableLooper.get(this)
+        // Apply only the values of the theme that are not defined
 
         testableLooper.runWithLooper {
-            qsPanel = QSPanel(context, null)
+            qsPanel = QSPanel(themedContext, null)
             qsPanel.mUsingMediaPlayer = true
 
             qsPanel.initialize(qsLogger)
             // QSPanel inflates a footer inside of it, mocking it here
-            footer = LinearLayout(context).apply { id = R.id.qs_footer }
+            footer = LinearLayout(themedContext).apply { id = R.id.qs_footer }
             qsPanel.addView(footer, MATCH_PARENT, 100)
             qsPanel.onFinishInflate()
             // Provides a parent with non-zero size for QSPanel
@@ -105,12 +112,12 @@
         qsPanel.tileLayout?.addTile(
                 QSPanelControllerBase.TileRecord(
                     mock(QSTile::class.java),
-                    QSTileViewImpl(context, QSIconViewImpl(context))
+                    QSTileViewImpl(themedContext, QSIconViewImpl(themedContext))
                 )
             )
 
-        val mediaView = FrameLayout(context)
-        mediaView.addView(View(context), MATCH_PARENT, 800)
+        val mediaView = FrameLayout(themedContext)
+        mediaView.addView(View(themedContext), MATCH_PARENT, 800)
 
         qsPanel.setUsingHorizontalLayout(/* horizontal */ true, mediaView, /* force */ true)
         qsPanel.measure(
@@ -135,12 +142,12 @@
         qsPanel.tileLayout?.addTile(
             QSPanelControllerBase.TileRecord(
                 mock(QSTile::class.java),
-                QSTileViewImpl(context, QSIconViewImpl(context))
+                QSTileViewImpl(themedContext, QSIconViewImpl(themedContext))
             )
         )
 
-        val mediaView = FrameLayout(context)
-        mediaView.addView(View(context), MATCH_PARENT, 800)
+        val mediaView = FrameLayout(themedContext)
+        mediaView.addView(View(themedContext), MATCH_PARENT, 800)
 
         qsPanel.setUsingHorizontalLayout(/* horizontal */ true, mediaView, /* force */ true)
         qsPanel.measure(
@@ -161,7 +168,10 @@
     @Test
     fun testBottomPadding() {
         val padding = 10
-        context.orCreateTestableResources.addOverride(R.dimen.qs_panel_padding_bottom, padding)
+        themedContext.orCreateTestableResources.addOverride(
+                R.dimen.qs_panel_padding_bottom,
+                padding
+        )
         qsPanel.updatePadding()
         assertThat(qsPanel.paddingBottom).isEqualTo(padding)
     }
@@ -170,8 +180,11 @@
     fun testTopPadding() {
         val padding = 10
         val paddingCombined = 100
-        context.orCreateTestableResources.addOverride(R.dimen.qs_panel_padding_top, padding)
-        context.orCreateTestableResources.addOverride(R.dimen.qs_panel_padding_top, paddingCombined)
+        themedContext.orCreateTestableResources.addOverride(R.dimen.qs_panel_padding_top, padding)
+        themedContext.orCreateTestableResources.addOverride(
+                R.dimen.qs_panel_padding_top,
+                paddingCombined
+        )
 
         qsPanel.updatePadding()
         assertThat(qsPanel.paddingTop).isEqualTo(paddingCombined)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSTileHostTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSTileHostTest.java
index d98bcee..9781baa 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSTileHostTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSTileHostTest.java
@@ -25,6 +25,7 @@
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.anyString;
 import static org.mockito.ArgumentMatchers.isNull;
+import static org.mockito.Mockito.clearInvocations;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.verify;
@@ -107,7 +108,7 @@
     @Mock
     private TunerService mTunerService;
     @Mock
-    private Provider<AutoTileManager> mAutoTiles;
+    private AutoTileManager mAutoTiles;
     @Mock
     private ShadeController mShadeController;
     @Mock
@@ -140,6 +141,7 @@
         mFeatureFlags = new FakeFeatureFlags();
 
         mFeatureFlags.set(Flags.QS_PIPELINE_NEW_HOST, false);
+        mFeatureFlags.set(Flags.QS_PIPELINE_AUTO_ADD, false);
 
         mMainExecutor = new FakeExecutor(new FakeSystemClock());
 
@@ -160,9 +162,10 @@
         mSecureSettings = new FakeSettings();
         saveSetting("");
         mQSTileHost = new TestQSTileHost(mContext, mDefaultFactory, mMainExecutor,
-                mPluginManager, mTunerService, mAutoTiles, mShadeController,
+                mPluginManager, mTunerService, () -> mAutoTiles, mShadeController,
                 mQSLogger, mUserTracker, mSecureSettings, mCustomTileStatePersister,
                 mTileLifecycleManagerFactory, mUserFileManager, mFeatureFlags);
+        mMainExecutor.runAllReady();
 
         mSecureSettings.registerContentObserverForUser(SETTING, new ContentObserver(null) {
             @Override
@@ -296,11 +299,20 @@
         StringWriter w = new StringWriter();
         PrintWriter pw = new PrintWriter(w);
         mQSTileHost.dump(pw, new String[]{});
-        String output = "QSTileHost:\n"
-                + TestTile1.class.getSimpleName() + ":\n"
-                + "    " + MOCK_STATE_STRING + "\n"
-                + TestTile2.class.getSimpleName() + ":\n"
-                + "    " + MOCK_STATE_STRING + "\n";
+
+        String output = "QSTileHost:" + "\n"
+                + "tile specs: [spec1, spec2]" + "\n"
+                + "current user: 0" + "\n"
+                + "is dirty: false" + "\n"
+                + "tiles:" + "\n"
+                + "TestTile1:" + "\n"
+                + "    MockState" + "\n"
+                + "TestTile2:" + "\n"
+                + "    MockState" + "\n";
+
+        System.out.println(output);
+        System.out.println(w.getBuffer().toString());
+
         assertEquals(output, w.getBuffer().toString());
     }
 
@@ -672,6 +684,17 @@
         assertEquals(CUSTOM_TILE.getClassName(), proto.tiles[1].getComponentName().className);
     }
 
+    @Test
+    public void testUserChange_flagOn_autoTileManagerNotified() {
+        mFeatureFlags.set(Flags.QS_PIPELINE_NEW_HOST, true);
+        int currentUser = mUserTracker.getUserId();
+        clearInvocations(mAutoTiles);
+        when(mUserTracker.getUserId()).thenReturn(currentUser + 1);
+
+        mQSTileHost.onTuningChanged(SETTING, "a,b");
+        verify(mAutoTiles).changeUser(UserHandle.of(currentUser + 1));
+    }
+
     private SharedPreferences getSharedPreferencesForUser(int user) {
         return mUserFileManager.getSharedPreferences(QSTileHost.TILES, 0, user);
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/TileLayoutTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/TileLayoutTest.java
index 8789253..f55ef65 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/TileLayoutTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/TileLayoutTest.java
@@ -26,12 +26,14 @@
 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.content.Context;
 import android.content.res.Configuration;
 import android.content.res.Resources;
 import android.test.suitebuilder.annotation.SmallTest;
 import android.testing.TestableLooper;
+import android.view.ContextThemeWrapper;
 import android.view.View;
 import android.view.accessibility.AccessibilityNodeInfo;
 
@@ -56,14 +58,17 @@
     private Resources mResources;
     private int mLayoutSizeForOneTile;
     private TileLayout mTileLayout; // under test
+    private Context mSpyContext;
+
 
     @Before
     public void setUp() throws Exception {
-        Context context = Mockito.spy(mContext);
-        mResources = Mockito.spy(context.getResources());
-        Mockito.when(mContext.getResources()).thenReturn(mResources);
+        mSpyContext = Mockito.spy(
+                new ContextThemeWrapper(mContext, R.style.Theme_SystemUI_QuickSettings));
+        mResources = Mockito.spy(mSpyContext.getResources());
+        when(mSpyContext.getResources()).thenReturn(mResources);
 
-        mTileLayout = new TileLayout(context);
+        mTileLayout = new TileLayout(mSpyContext);
         // Layout needs to leave space for the tile margins. Three times the margin size is
         // sufficient for any number of columns.
         mLayoutSizeForOneTile =
@@ -73,7 +78,7 @@
     private QSPanelControllerBase.TileRecord createTileRecord() {
         return new QSPanelControllerBase.TileRecord(
                 mock(QSTile.class),
-                spy(new QSTileViewImpl(mContext, new QSIconViewImpl(mContext))));
+                spy(new QSTileViewImpl(mSpyContext, new QSIconViewImpl(mSpyContext))));
     }
 
     @Test
@@ -161,7 +166,7 @@
                 .layout(left2.capture(), top2.capture(), right2.capture(), bottom2.capture());
 
         // We assume two tiles will always fit side-by-side.
-        assertTrue(mContext.getResources().getInteger(R.integer.quick_settings_num_columns) > 1);
+        assertTrue(mSpyContext.getResources().getInteger(R.integer.quick_settings_num_columns) > 1);
 
         // left <= right, top <= bottom
         assertTrue(left1.getValue() <= right1.getValue());
@@ -218,16 +223,16 @@
 
     @Test
     public void resourcesChanged_updateResources_returnsTrue() {
-        Mockito.when(mResources.getInteger(R.integer.quick_settings_num_columns)).thenReturn(1);
+        when(mResources.getInteger(R.integer.quick_settings_num_columns)).thenReturn(1);
         mTileLayout.updateResources(); // setup with 1
-        Mockito.when(mResources.getInteger(R.integer.quick_settings_num_columns)).thenReturn(2);
+        when(mResources.getInteger(R.integer.quick_settings_num_columns)).thenReturn(2);
 
         assertEquals(true, mTileLayout.updateResources());
     }
 
     @Test
     public void resourcesSame_updateResources_returnsFalse() {
-        Mockito.when(mResources.getInteger(R.integer.quick_settings_num_columns)).thenReturn(1);
+        when(mResources.getInteger(R.integer.quick_settings_num_columns)).thenReturn(1);
         mTileLayout.updateResources(); // setup with 1
 
         assertEquals(false, mTileLayout.updateResources());
@@ -250,7 +255,7 @@
         QSPanelControllerBase.TileRecord tileRecord = createTileRecord();
         mTileLayout.addTile(tileRecord);
 
-        FakeTileView tileView = new FakeTileView(mContext);
+        FakeTileView tileView = new FakeTileView(mSpyContext);
         QSTile.State state = new QSTile.State();
         state.label = "TEST LABEL";
         state.secondaryLabel = "TEST SECONDARY LABEL";
@@ -276,9 +281,10 @@
     }
 
     private void changeFontScaling(float scale) {
-        Configuration configuration = new Configuration(mContext.getResources().getConfiguration());
+        Configuration configuration =
+                new Configuration(mSpyContext.getResources().getConfiguration());
         configuration.fontScale = scale;
         // updateConfiguration could help update on both resource configuration and displayMetrics
-        mContext.getResources().updateConfiguration(configuration, null, null);
+        mSpyContext.getResources().updateConfiguration(configuration, null, null);
     }
 }
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 2cc6709..d647d6a 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
@@ -21,6 +21,7 @@
 import android.testing.AndroidTestingRunner
 import android.testing.TestableLooper
 import android.testing.TestableLooper.RunWithLooper
+import android.view.ContextThemeWrapper
 import androidx.test.filters.SmallTest
 import com.android.settingslib.Utils
 import com.android.settingslib.drawable.UserIconDrawable
@@ -63,6 +64,8 @@
     private val testScope = TestScope()
     private lateinit var utils: FooterActionsTestUtils
 
+    private val themedContext = ContextThemeWrapper(context, R.style.Theme_SystemUI_QuickSettings)
+
     @Before
     fun setUp() {
         utils = FooterActionsTestUtils(context, TestableLooper.get(this), testScope.testScheduler)
@@ -84,8 +87,14 @@
                     ContentDescription.Resource(R.string.accessibility_quick_settings_settings)
                 )
             )
-        assertThat(settings.backgroundColor).isEqualTo(R.attr.offStateColor)
-        assertThat(settings.iconTint).isNull()
+        assertThat(settings.backgroundColor).isEqualTo(R.attr.shadeInactive)
+        assertThat(settings.iconTint)
+            .isEqualTo(
+                Utils.getColorAttrDefaultColor(
+                    themedContext,
+                    R.attr.onShadeInactiveVariant,
+                )
+            )
     }
 
     @Test
@@ -105,12 +114,12 @@
                     ContentDescription.Resource(R.string.accessibility_quick_settings_power_menu)
                 )
             )
-        assertThat(power.backgroundColor).isEqualTo(com.android.internal.R.attr.colorAccent)
+        assertThat(power.backgroundColor).isEqualTo(R.attr.shadeActive)
         assertThat(power.iconTint)
             .isEqualTo(
                 Utils.getColorAttrDefaultColor(
-                    context,
-                    com.android.internal.R.attr.textColorOnAccent,
+                    themedContext,
+                    R.attr.onShadeActive,
                 ),
             )
     }
@@ -170,7 +179,7 @@
         assertThat(userSwitcher).isNotNull()
         assertThat(userSwitcher!!.icon)
             .isEqualTo(Icon.Loaded(picture, ContentDescription.Loaded("Signed in as foo")))
-        assertThat(userSwitcher.backgroundColor).isEqualTo(R.attr.offStateColor)
+        assertThat(userSwitcher.backgroundColor).isEqualTo(R.attr.shadeInactive)
 
         // Change the current user name.
         userSwitcherControllerWrapper.currentUserName = "bar"
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/autoaddable/AutoAddableSettingListTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/autoaddable/AutoAddableSettingListTest.kt
new file mode 100644
index 0000000..817ac61
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/autoaddable/AutoAddableSettingListTest.kt
@@ -0,0 +1,99 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.pipeline.domain.autoaddable
+
+import android.content.ComponentName
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.systemui.R
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.qs.pipeline.shared.TileSpec
+import com.android.systemui.util.mockito.mock
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class AutoAddableSettingListTest : SysuiTestCase() {
+
+    private val factory =
+        object : AutoAddableSetting.Factory {
+            override fun create(setting: String, spec: TileSpec): AutoAddableSetting {
+                return AutoAddableSetting(
+                    mock(),
+                    mock(),
+                    setting,
+                    spec,
+                )
+            }
+        }
+
+    @Test
+    fun correctLines_correctAutoAddables() {
+        val setting1 = "setting1"
+        val setting2 = "setting2"
+        val spec1 = TileSpec.create("spec1")
+        val spec2 = TileSpec.create(ComponentName("pkg", "cls"))
+
+        context.orCreateTestableResources.addOverride(
+            R.array.config_quickSettingsAutoAdd,
+            arrayOf(toStringLine(setting1, spec1), toStringLine(setting2, spec2))
+        )
+
+        val autoAddables = AutoAddableSettingList.parseSettingsResource(context.resources, factory)
+
+        assertThat(autoAddables)
+            .containsExactly(factory.create(setting1, spec1), factory.create(setting2, spec2))
+    }
+
+    @Test
+    fun malformedLine_ignored() {
+        val setting = "setting"
+        val spec = TileSpec.create("spec")
+
+        context.orCreateTestableResources.addOverride(
+            R.array.config_quickSettingsAutoAdd,
+            arrayOf(toStringLine(setting, spec), "bad_line")
+        )
+
+        val autoAddables = AutoAddableSettingList.parseSettingsResource(context.resources, factory)
+
+        assertThat(autoAddables).containsExactly(factory.create(setting, spec))
+    }
+
+    @Test
+    fun invalidSpec_ignored() {
+        val setting = "setting"
+        val spec = TileSpec.create("spec")
+
+        context.orCreateTestableResources.addOverride(
+            R.array.config_quickSettingsAutoAdd,
+            arrayOf(toStringLine(setting, spec), "invalid:")
+        )
+
+        val autoAddables = AutoAddableSettingList.parseSettingsResource(context.resources, factory)
+
+        assertThat(autoAddables).containsExactly(factory.create(setting, spec))
+    }
+
+    companion object {
+        private fun toStringLine(setting: String, spec: TileSpec) =
+            "$setting$SETTINGS_SEPARATOR${spec.spec}"
+        private const val SETTINGS_SEPARATOR = ":"
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/autoaddable/AutoAddableSettingTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/autoaddable/AutoAddableSettingTest.kt
new file mode 100644
index 0000000..36c3c9d
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/autoaddable/AutoAddableSettingTest.kt
@@ -0,0 +1,117 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.pipeline.domain.autoaddable
+
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.coroutines.collectValues
+import com.android.systemui.qs.pipeline.domain.model.AutoAddSignal
+import com.android.systemui.qs.pipeline.domain.model.AutoAddTracking
+import com.android.systemui.qs.pipeline.shared.TileSpec
+import com.android.systemui.util.settings.FakeSettings
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.StandardTestDispatcher
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class AutoAddableSettingTest : SysuiTestCase() {
+
+    private val testDispatcher = StandardTestDispatcher()
+    private val testScope = TestScope(testDispatcher)
+
+    private val secureSettings = FakeSettings()
+    private val underTest =
+        AutoAddableSetting(
+            secureSettings,
+            testDispatcher,
+            SETTING,
+            SPEC,
+        )
+
+    @Test
+    fun settingNotSet_noSignal() =
+        testScope.runTest {
+            val userId = 0
+            val signal by collectLastValue(underTest.autoAddSignal(userId))
+
+            assertThat(signal).isNull() // null means no emitted value
+        }
+
+    @Test
+    fun settingSetTo0_noSignal() =
+        testScope.runTest {
+            val userId = 0
+            val signal by collectLastValue(underTest.autoAddSignal(userId))
+
+            secureSettings.putIntForUser(SETTING, 0, userId)
+
+            assertThat(signal).isNull() // null means no emitted value
+        }
+
+    @Test
+    fun settingSetToNon0_signal() =
+        testScope.runTest {
+            val userId = 0
+            val signal by collectLastValue(underTest.autoAddSignal(userId))
+
+            secureSettings.putIntForUser(SETTING, 42, userId)
+
+            assertThat(signal).isEqualTo(AutoAddSignal.Add(SPEC))
+        }
+
+    @Test
+    fun settingSetForUser_onlySignalInThatUser() =
+        testScope.runTest {
+            val signal0 by collectLastValue(underTest.autoAddSignal(0))
+            val signal1 by collectLastValue(underTest.autoAddSignal(1))
+
+            secureSettings.putIntForUser(SETTING, /* value */ 42, /* userHandle */ 1)
+
+            assertThat(signal0).isNull()
+            assertThat(signal1).isEqualTo(AutoAddSignal.Add(SPEC))
+        }
+
+    @Test
+    fun multipleNonZeroChanges_onlyOneSignal() =
+        testScope.runTest {
+            val userId = 0
+            val signals by collectValues(underTest.autoAddSignal(userId))
+
+            secureSettings.putIntForUser(SETTING, 1, userId)
+            secureSettings.putIntForUser(SETTING, 2, userId)
+
+            assertThat(signals.size).isEqualTo(1)
+        }
+
+    @Test
+    fun strategyIfNotAdded() {
+        assertThat(underTest.autoAddTracking).isEqualTo(AutoAddTracking.IfNotAdded(SPEC))
+    }
+
+    companion object {
+        private const val SETTING = "setting"
+        private val SPEC = TileSpec.create("spec")
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/autoaddable/CallbackControllerAutoAddableTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/autoaddable/CallbackControllerAutoAddableTest.kt
new file mode 100644
index 0000000..afb43c7
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/autoaddable/CallbackControllerAutoAddableTest.kt
@@ -0,0 +1,139 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.pipeline.domain.autoaddable
+
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.qs.pipeline.domain.model.AutoAddSignal
+import com.android.systemui.qs.pipeline.domain.model.AutoAddTracking
+import com.android.systemui.qs.pipeline.shared.TileSpec
+import com.android.systemui.statusbar.policy.CallbackController
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.channels.ProducerScope
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class CallbackControllerAutoAddableTest : SysuiTestCase() {
+
+    @Test
+    fun callbackAddedAndRemoved() = runTest {
+        val controller = TestableController()
+        val callback = object : TestableController.Callback {}
+        val underTest =
+            object :
+                CallbackControllerAutoAddable<TestableController.Callback, TestableController>(
+                    controller
+                ) {
+                override val description: String = ""
+                override val spec: TileSpec
+                    get() = SPEC
+
+                override fun ProducerScope<AutoAddSignal>.getCallback():
+                    TestableController.Callback {
+                    return callback
+                }
+            }
+
+        val job = launch { underTest.autoAddSignal(0).collect {} }
+        runCurrent()
+        assertThat(controller.callbacks).containsExactly(callback)
+        job.cancel()
+        runCurrent()
+        assertThat(controller.callbacks).isEmpty()
+    }
+
+    @Test
+    fun sendAddFromCallback() = runTest {
+        val controller = TestableController()
+        val underTest =
+            object :
+                CallbackControllerAutoAddable<TestableController.Callback, TestableController>(
+                    controller
+                ) {
+                override val description: String = ""
+
+                override val spec: TileSpec
+                    get() = SPEC
+
+                override fun ProducerScope<AutoAddSignal>.getCallback():
+                    TestableController.Callback {
+                    return object : TestableController.Callback {
+                        override fun change() {
+                            sendAdd()
+                        }
+                    }
+                }
+            }
+
+        val signal by collectLastValue(underTest.autoAddSignal(0))
+        assertThat(signal).isNull()
+
+        controller.callbacks.first().change()
+
+        assertThat(signal).isEqualTo(AutoAddSignal.Add(SPEC))
+    }
+
+    @Test
+    fun strategyIfNotAdded() {
+        val underTest =
+            object :
+                CallbackControllerAutoAddable<TestableController.Callback, TestableController>(
+                    TestableController()
+                ) {
+                override val description: String = ""
+                override val spec: TileSpec
+                    get() = SPEC
+
+                override fun ProducerScope<AutoAddSignal>.getCallback():
+                    TestableController.Callback {
+                    return object : TestableController.Callback {}
+                }
+            }
+
+        assertThat(underTest.autoAddTracking).isEqualTo(AutoAddTracking.IfNotAdded(SPEC))
+    }
+
+    private class TestableController : CallbackController<TestableController.Callback> {
+
+        val callbacks = mutableSetOf<Callback>()
+
+        override fun addCallback(listener: Callback) {
+            callbacks.add(listener)
+        }
+
+        override fun removeCallback(listener: Callback) {
+            callbacks.remove(listener)
+        }
+
+        interface Callback {
+            fun change() {}
+        }
+    }
+
+    companion object {
+        private val SPEC = TileSpec.create("test")
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/autoaddable/CastAutoAddableTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/autoaddable/CastAutoAddableTest.kt
new file mode 100644
index 0000000..a357dad
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/autoaddable/CastAutoAddableTest.kt
@@ -0,0 +1,133 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.pipeline.domain.autoaddable
+
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.qs.pipeline.domain.model.AutoAddSignal
+import com.android.systemui.qs.pipeline.shared.TileSpec
+import com.android.systemui.qs.tiles.CastTile
+import com.android.systemui.statusbar.policy.CastController
+import com.android.systemui.util.mockito.capture
+import com.android.systemui.util.mockito.whenever
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentCaptor
+import org.mockito.Captor
+import org.mockito.Mock
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class CastAutoAddableTest : SysuiTestCase() {
+
+    @Mock private lateinit var castController: CastController
+    @Captor private lateinit var callbackCaptor: ArgumentCaptor<CastController.Callback>
+
+    private lateinit var underTest: CastAutoAddable
+
+    @Before
+    fun setUp() {
+        MockitoAnnotations.initMocks(this)
+
+        underTest = CastAutoAddable(castController)
+    }
+
+    @Test
+    fun onCastDevicesChanged_noDevices_noSignal() = runTest {
+        val signal by collectLastValue(underTest.autoAddSignal(0))
+        runCurrent()
+
+        verify(castController).addCallback(capture(callbackCaptor))
+
+        callbackCaptor.value.onCastDevicesChanged()
+
+        assertThat(signal).isNull()
+    }
+
+    @Test
+    fun onCastDevicesChanged_deviceNotConnectedOrConnecting_noSignal() = runTest {
+        val device =
+            CastController.CastDevice().apply {
+                state = CastController.CastDevice.STATE_DISCONNECTED
+            }
+        whenever(castController.castDevices).thenReturn(listOf(device))
+
+        val signal by collectLastValue(underTest.autoAddSignal(0))
+        runCurrent()
+
+        verify(castController).addCallback(capture(callbackCaptor))
+
+        callbackCaptor.value.onCastDevicesChanged()
+
+        assertThat(signal).isNull()
+    }
+
+    @Test
+    fun onCastDevicesChanged_someDeviceConnecting_addSignal() = runTest {
+        val disconnectedDevice =
+            CastController.CastDevice().apply {
+                state = CastController.CastDevice.STATE_DISCONNECTED
+            }
+        val connectingDevice =
+            CastController.CastDevice().apply { state = CastController.CastDevice.STATE_CONNECTING }
+        whenever(castController.castDevices)
+            .thenReturn(listOf(disconnectedDevice, connectingDevice))
+
+        val signal by collectLastValue(underTest.autoAddSignal(0))
+        runCurrent()
+
+        verify(castController).addCallback(capture(callbackCaptor))
+
+        callbackCaptor.value.onCastDevicesChanged()
+
+        assertThat(signal).isEqualTo(AutoAddSignal.Add(SPEC))
+    }
+
+    @Test
+    fun onCastDevicesChanged_someDeviceConnected_addSignal() = runTest {
+        val disconnectedDevice =
+            CastController.CastDevice().apply {
+                state = CastController.CastDevice.STATE_DISCONNECTED
+            }
+        val connectedDevice =
+            CastController.CastDevice().apply { state = CastController.CastDevice.STATE_CONNECTED }
+        whenever(castController.castDevices).thenReturn(listOf(disconnectedDevice, connectedDevice))
+
+        val signal by collectLastValue(underTest.autoAddSignal(0))
+        runCurrent()
+
+        verify(castController).addCallback(capture(callbackCaptor))
+
+        callbackCaptor.value.onCastDevicesChanged()
+
+        assertThat(signal).isEqualTo(AutoAddSignal.Add(SPEC))
+    }
+
+    companion object {
+        private val SPEC = TileSpec.create(CastTile.TILE_SPEC)
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/autoaddable/DataSaverAutoAddableTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/autoaddable/DataSaverAutoAddableTest.kt
new file mode 100644
index 0000000..098ffc3
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/autoaddable/DataSaverAutoAddableTest.kt
@@ -0,0 +1,85 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.pipeline.domain.autoaddable
+
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.qs.pipeline.domain.model.AutoAddSignal
+import com.android.systemui.qs.pipeline.shared.TileSpec
+import com.android.systemui.qs.tiles.DataSaverTile
+import com.android.systemui.statusbar.policy.DataSaverController
+import com.android.systemui.util.mockito.capture
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentCaptor
+import org.mockito.Captor
+import org.mockito.Mock
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class DataSaverAutoAddableTest : SysuiTestCase() {
+
+    @Mock private lateinit var dataSaverController: DataSaverController
+    @Captor private lateinit var callbackCaptor: ArgumentCaptor<DataSaverController.Listener>
+
+    private lateinit var underTest: DataSaverAutoAddable
+
+    @Before
+    fun setUp() {
+        MockitoAnnotations.initMocks(this)
+
+        underTest = DataSaverAutoAddable(dataSaverController)
+    }
+
+    @Test
+    fun dataSaverNotEnabled_NoSignal() = runTest {
+        val signal by collectLastValue(underTest.autoAddSignal(0))
+        runCurrent()
+
+        verify(dataSaverController).addCallback(capture(callbackCaptor))
+
+        callbackCaptor.value.onDataSaverChanged(false)
+
+        assertThat(signal).isNull()
+    }
+
+    @Test
+    fun dataSaverEnabled_addSignal() = runTest {
+        val signal by collectLastValue(underTest.autoAddSignal(0))
+        runCurrent()
+
+        verify(dataSaverController).addCallback(capture(callbackCaptor))
+
+        callbackCaptor.value.onDataSaverChanged(true)
+
+        assertThat(signal).isEqualTo(AutoAddSignal.Add(SPEC))
+    }
+
+    companion object {
+        private val SPEC = TileSpec.create(DataSaverTile.TILE_SPEC)
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/autoaddable/DeviceControlsAutoAddableTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/autoaddable/DeviceControlsAutoAddableTest.kt
new file mode 100644
index 0000000..a2e3538
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/autoaddable/DeviceControlsAutoAddableTest.kt
@@ -0,0 +1,115 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.pipeline.domain.autoaddable
+
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.qs.pipeline.domain.model.AutoAddSignal
+import com.android.systemui.qs.pipeline.domain.model.AutoAddTracking
+import com.android.systemui.qs.pipeline.shared.TileSpec
+import com.android.systemui.qs.tiles.DeviceControlsTile
+import com.android.systemui.statusbar.policy.DeviceControlsController
+import com.android.systemui.util.mockito.capture
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.collect
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentCaptor
+import org.mockito.Captor
+import org.mockito.Mock
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class DeviceControlsAutoAddableTest : SysuiTestCase() {
+
+    @Mock private lateinit var deviceControlsController: DeviceControlsController
+    @Captor private lateinit var callbackCaptor: ArgumentCaptor<DeviceControlsController.Callback>
+
+    private lateinit var underTest: DeviceControlsAutoAddable
+
+    @Before
+    fun setUp() {
+        MockitoAnnotations.initMocks(this)
+
+        underTest = DeviceControlsAutoAddable(deviceControlsController)
+    }
+
+    @Test
+    fun strategyAlways() {
+        assertThat(underTest.autoAddTracking).isEqualTo(AutoAddTracking.Always)
+    }
+
+    @Test
+    fun onControlsUpdate_position_addSignal() = runTest {
+        val signal by collectLastValue(underTest.autoAddSignal(0))
+        val position = 5
+        runCurrent()
+
+        verify(deviceControlsController).setCallback(capture(callbackCaptor))
+        callbackCaptor.value.onControlsUpdate(position)
+
+        assertThat(signal).isEqualTo(AutoAddSignal.Add(SPEC, position))
+        verify(deviceControlsController).removeCallback()
+    }
+
+    @Test
+    fun onControlsUpdate_nullPosition_noAddSignal() = runTest {
+        val signal by collectLastValue(underTest.autoAddSignal(0))
+        runCurrent()
+
+        verify(deviceControlsController).setCallback(capture(callbackCaptor))
+        callbackCaptor.value.onControlsUpdate(null)
+
+        assertThat(signal).isNull()
+        verify(deviceControlsController).removeCallback()
+    }
+
+    @Test
+    fun onRemoveControlsAutoTracker_removeSignal() = runTest {
+        val signal by collectLastValue(underTest.autoAddSignal(0))
+        runCurrent()
+
+        verify(deviceControlsController).setCallback(capture(callbackCaptor))
+        callbackCaptor.value.removeControlsAutoTracker()
+
+        assertThat(signal).isEqualTo(AutoAddSignal.Remove(SPEC))
+    }
+
+    @Test
+    fun flowCancelled_removeCallback() = runTest {
+        val job = launch { underTest.autoAddSignal(0).collect() }
+        runCurrent()
+
+        job.cancel()
+        runCurrent()
+        verify(deviceControlsController).removeCallback()
+    }
+
+    companion object {
+        private val SPEC = TileSpec.create(DeviceControlsTile.TILE_SPEC)
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/autoaddable/HotspotAutoAddableTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/autoaddable/HotspotAutoAddableTest.kt
new file mode 100644
index 0000000..ee96b47
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/autoaddable/HotspotAutoAddableTest.kt
@@ -0,0 +1,83 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.pipeline.domain.autoaddable
+
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.qs.pipeline.domain.model.AutoAddSignal
+import com.android.systemui.qs.pipeline.shared.TileSpec
+import com.android.systemui.qs.tiles.HotspotTile
+import com.android.systemui.statusbar.policy.HotspotController
+import com.android.systemui.util.mockito.capture
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentCaptor
+import org.mockito.Captor
+import org.mockito.Mock
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class HotspotAutoAddableTest : SysuiTestCase() {
+
+    @Mock private lateinit var hotspotController: HotspotController
+    @Captor private lateinit var callbackCaptor: ArgumentCaptor<HotspotController.Callback>
+
+    private lateinit var underTest: HotspotAutoAddable
+
+    @Before
+    fun setUp() {
+        MockitoAnnotations.initMocks(this)
+
+        underTest = HotspotAutoAddable(hotspotController)
+    }
+
+    @Test
+    fun enabled_addSignal() = runTest {
+        val signal by collectLastValue(underTest.autoAddSignal(0))
+        runCurrent()
+
+        verify(hotspotController).addCallback(capture(callbackCaptor))
+        callbackCaptor.value.onHotspotChanged(/* enabled = */ true, /* numDevices = */ 5)
+
+        assertThat(signal).isEqualTo(AutoAddSignal.Add(SPEC))
+    }
+
+    @Test
+    fun notEnabled_noSignal() = runTest {
+        val signal by collectLastValue(underTest.autoAddSignal(0))
+        runCurrent()
+
+        verify(hotspotController).addCallback(capture(callbackCaptor))
+        callbackCaptor.value.onHotspotChanged(/* enabled = */ false, /* numDevices = */ 0)
+
+        assertThat(signal).isNull()
+    }
+
+    companion object {
+        private val SPEC = TileSpec.create(HotspotTile.TILE_SPEC)
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/autoaddable/NightDisplayAutoAddableTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/autoaddable/NightDisplayAutoAddableTest.kt
new file mode 100644
index 0000000..e03072a
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/autoaddable/NightDisplayAutoAddableTest.kt
@@ -0,0 +1,126 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.pipeline.domain.autoaddable
+
+import android.hardware.display.NightDisplayListener
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.dagger.NightDisplayListenerModule
+import com.android.systemui.qs.pipeline.domain.model.AutoAddSignal
+import com.android.systemui.qs.pipeline.domain.model.AutoAddTracking
+import com.android.systemui.qs.pipeline.shared.TileSpec
+import com.android.systemui.qs.tiles.NightDisplayTile
+import com.android.systemui.util.mockito.capture
+import com.android.systemui.util.mockito.whenever
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.collect
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.test.TestResult
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Answers
+import org.mockito.ArgumentCaptor
+import org.mockito.Captor
+import org.mockito.Mock
+import org.mockito.Mockito.inOrder
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class NightDisplayAutoAddableTest : SysuiTestCase() {
+
+    @Mock(answer = Answers.RETURNS_SELF)
+    private lateinit var nightDisplayListenerBuilder: NightDisplayListenerModule.Builder
+    @Mock private lateinit var nightDisplayListener: NightDisplayListener
+    @Captor private lateinit var callbackCaptor: ArgumentCaptor<NightDisplayListener.Callback>
+
+    private lateinit var underTest: NightDisplayAutoAddable
+
+    @Before
+    fun setUp() {
+        MockitoAnnotations.initMocks(this)
+
+        whenever(nightDisplayListenerBuilder.build()).thenReturn(nightDisplayListener)
+    }
+
+    @Test
+    fun disabled_strategyDisabled() =
+        testWithFeatureAvailability(enabled = false) {
+            assertThat(underTest.autoAddTracking).isEqualTo(AutoAddTracking.Disabled)
+        }
+
+    @Test
+    fun enabled_strategyIfNotAdded() = testWithFeatureAvailability {
+        assertThat(underTest.autoAddTracking).isEqualTo(AutoAddTracking.IfNotAdded(SPEC))
+    }
+
+    @Test
+    fun listenerCreatedForCorrectUser() = testWithFeatureAvailability {
+        val user = 42
+        backgroundScope.launch { underTest.autoAddSignal(user).collect() }
+        runCurrent()
+
+        val inOrder = inOrder(nightDisplayListenerBuilder)
+        inOrder.verify(nightDisplayListenerBuilder).setUser(user)
+        inOrder.verify(nightDisplayListenerBuilder).build()
+    }
+
+    @Test
+    fun onCancelFlow_removeCallback() = testWithFeatureAvailability {
+        val job = launch { underTest.autoAddSignal(0).collect() }
+        runCurrent()
+        job.cancel()
+        runCurrent()
+        verify(nightDisplayListener).setCallback(null)
+    }
+
+    @Test
+    fun onActivatedTrue_addSignal() = testWithFeatureAvailability {
+        val signal by collectLastValue(underTest.autoAddSignal(0))
+        runCurrent()
+
+        verify(nightDisplayListener).setCallback(capture(callbackCaptor))
+        callbackCaptor.value.onActivated(true)
+
+        assertThat(signal).isEqualTo(AutoAddSignal.Add(SPEC))
+    }
+
+    private fun testWithFeatureAvailability(
+        enabled: Boolean = true,
+        body: suspend TestScope.() -> TestResult
+    ) = runTest {
+        context.orCreateTestableResources.addOverride(
+            com.android.internal.R.bool.config_nightDisplayAvailable,
+            enabled
+        )
+        underTest = NightDisplayAutoAddable(nightDisplayListenerBuilder, context)
+        body()
+    }
+
+    companion object {
+        private val SPEC = TileSpec.create(NightDisplayTile.TILE_SPEC)
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/autoaddable/ReduceBrightColorsAutoAddableTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/autoaddable/ReduceBrightColorsAutoAddableTest.kt
new file mode 100644
index 0000000..7b4a55e
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/autoaddable/ReduceBrightColorsAutoAddableTest.kt
@@ -0,0 +1,108 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.pipeline.domain.autoaddable
+
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.qs.ReduceBrightColorsController
+import com.android.systemui.qs.pipeline.domain.model.AutoAddSignal
+import com.android.systemui.qs.pipeline.domain.model.AutoAddTracking
+import com.android.systemui.qs.pipeline.shared.TileSpec
+import com.android.systemui.qs.tiles.ReduceBrightColorsTile
+import com.android.systemui.util.mockito.capture
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.TestResult
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentCaptor
+import org.mockito.Captor
+import org.mockito.Mock
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class ReduceBrightColorsAutoAddableTest : SysuiTestCase() {
+
+    @Mock private lateinit var reduceBrightColorsController: ReduceBrightColorsController
+    @Captor
+    private lateinit var reduceBrightColorsListenerCaptor:
+        ArgumentCaptor<ReduceBrightColorsController.Listener>
+
+    private lateinit var underTest: ReduceBrightColorsAutoAddable
+
+    @Before
+    fun setUp() {
+        MockitoAnnotations.initMocks(this)
+    }
+
+    @Test
+    fun notAvailable_strategyDisabled() =
+        testWithFeatureAvailability(available = false) {
+            assertThat(underTest.autoAddTracking).isEqualTo(AutoAddTracking.Disabled)
+        }
+
+    @Test
+    fun available_strategyIfNotAdded() =
+        testWithFeatureAvailability(available = true) {
+            assertThat(underTest.autoAddTracking).isEqualTo(AutoAddTracking.IfNotAdded(SPEC))
+        }
+
+    @Test
+    fun activated_addSignal() = testWithFeatureAvailability {
+        val signal by collectLastValue(underTest.autoAddSignal(0))
+        runCurrent()
+
+        verify(reduceBrightColorsController).addCallback(capture(reduceBrightColorsListenerCaptor))
+
+        reduceBrightColorsListenerCaptor.value.onActivated(true)
+
+        assertThat(signal).isEqualTo(AutoAddSignal.Add(SPEC))
+    }
+
+    @Test
+    fun notActivated_noSignal() = testWithFeatureAvailability {
+        val signal by collectLastValue(underTest.autoAddSignal(0))
+        runCurrent()
+
+        verify(reduceBrightColorsController).addCallback(capture(reduceBrightColorsListenerCaptor))
+
+        reduceBrightColorsListenerCaptor.value.onActivated(false)
+
+        assertThat(signal).isNull()
+    }
+
+    private fun testWithFeatureAvailability(
+        available: Boolean = true,
+        body: suspend TestScope.() -> TestResult
+    ) = runTest {
+        underTest = ReduceBrightColorsAutoAddable(reduceBrightColorsController, available)
+        body()
+    }
+
+    companion object {
+        private val SPEC = TileSpec.create(ReduceBrightColorsTile.TILE_SPEC)
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/autoaddable/SafetyCenterAutoAddableTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/autoaddable/SafetyCenterAutoAddableTest.kt
new file mode 100644
index 0000000..fb35a3a
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/autoaddable/SafetyCenterAutoAddableTest.kt
@@ -0,0 +1,163 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.pipeline.domain.autoaddable
+
+import android.content.ComponentName
+import android.content.pm.PackageManager
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.systemui.R
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.coroutines.collectValues
+import com.android.systemui.qs.pipeline.domain.model.AutoAddSignal
+import com.android.systemui.qs.pipeline.domain.model.AutoAddTracking
+import com.android.systemui.qs.pipeline.shared.TileSpec
+import com.android.systemui.statusbar.policy.SafetyController
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.capture
+import com.android.systemui.util.mockito.whenever
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.collect
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.test.StandardTestDispatcher
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentCaptor
+import org.mockito.Captor
+import org.mockito.Mock
+import org.mockito.Mockito.never
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class SafetyCenterAutoAddableTest : SysuiTestCase() {
+    private val testDispatcher = StandardTestDispatcher()
+    private val testScope = TestScope(testDispatcher)
+
+    @Mock private lateinit var safetyController: SafetyController
+    @Mock private lateinit var packageManager: PackageManager
+    @Captor
+    private lateinit var safetyControllerListenerCaptor: ArgumentCaptor<SafetyController.Listener>
+
+    private lateinit var underTest: SafetyCenterAutoAddable
+
+    @Before
+    fun setUp() {
+        MockitoAnnotations.initMocks(this)
+        context.ensureTestableResources()
+
+        // Set these by default, will also test special cases
+        context.orCreateTestableResources.addOverride(
+            R.string.safety_quick_settings_tile_class,
+            SAFETY_TILE_CLASS_NAME
+        )
+        whenever(packageManager.permissionControllerPackageName)
+            .thenReturn(PERMISSION_CONTROLLER_PACKAGE_NAME)
+
+        underTest =
+            SafetyCenterAutoAddable(
+                safetyController,
+                packageManager,
+                context.resources,
+                testDispatcher,
+            )
+    }
+
+    @Test
+    fun strategyAlwaysTrack() =
+        testScope.runTest {
+            assertThat(underTest.autoAddTracking).isEqualTo(AutoAddTracking.Always)
+        }
+
+    @Test
+    fun tileAlwaysAdded() =
+        testScope.runTest {
+            val signal by collectLastValue(underTest.autoAddSignal(0))
+
+            assertThat(signal).isEqualTo(AutoAddSignal.Add(SPEC))
+        }
+
+    @Test
+    fun safetyCenterDisabled_removeSignal() =
+        testScope.runTest {
+            val signal by collectLastValue(underTest.autoAddSignal(0))
+            runCurrent()
+
+            verify(safetyController).addCallback(capture(safetyControllerListenerCaptor))
+            safetyControllerListenerCaptor.value.onSafetyCenterEnableChanged(false)
+
+            assertThat(signal).isEqualTo(AutoAddSignal.Remove(SPEC))
+        }
+
+    @Test
+    fun safetyCenterEnabled_newAddSignal() =
+        testScope.runTest {
+            val signals by collectValues(underTest.autoAddSignal(0))
+            runCurrent()
+
+            verify(safetyController).addCallback(capture(safetyControllerListenerCaptor))
+            safetyControllerListenerCaptor.value.onSafetyCenterEnableChanged(true)
+
+            assertThat(signals.size).isEqualTo(2)
+            assertThat(signals.last()).isEqualTo(AutoAddSignal.Add(SPEC))
+        }
+
+    @Test
+    fun flowCancelled_removeListener() =
+        testScope.runTest {
+            val job = launch { underTest.autoAddSignal(0).collect() }
+            runCurrent()
+
+            verify(safetyController).addCallback(capture(safetyControllerListenerCaptor))
+
+            job.cancel()
+            runCurrent()
+            verify(safetyController).removeCallback(safetyControllerListenerCaptor.value)
+        }
+
+    @Test
+    fun emptyClassName_noSignals() =
+        testScope.runTest {
+            context.orCreateTestableResources.addOverride(
+                R.string.safety_quick_settings_tile_class,
+                ""
+            )
+            val signal by collectLastValue(underTest.autoAddSignal(0))
+            runCurrent()
+
+            verify(safetyController, never()).addCallback(any())
+
+            assertThat(signal).isNull()
+        }
+
+    companion object {
+        private const val SAFETY_TILE_CLASS_NAME = "cls"
+        private const val PERMISSION_CONTROLLER_PACKAGE_NAME = "pkg"
+        private val SPEC =
+            TileSpec.create(
+                ComponentName(PERMISSION_CONTROLLER_PACKAGE_NAME, SAFETY_TILE_CLASS_NAME)
+            )
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/autoaddable/WalletAutoAddableTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/autoaddable/WalletAutoAddableTest.kt
new file mode 100644
index 0000000..6b250f4
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/autoaddable/WalletAutoAddableTest.kt
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.pipeline.domain.autoaddable
+
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.qs.pipeline.domain.model.AutoAddSignal
+import com.android.systemui.qs.pipeline.domain.model.AutoAddTracking
+import com.android.systemui.qs.pipeline.shared.TileSpec
+import com.android.systemui.qs.tiles.QuickAccessWalletTile
+import com.android.systemui.statusbar.policy.WalletController
+import com.android.systemui.util.mockito.whenever
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.MockitoAnnotations
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class WalletAutoAddableTest : SysuiTestCase() {
+
+    @Mock private lateinit var walletController: WalletController
+
+    private lateinit var underTest: WalletAutoAddable
+
+    @Before
+    fun setUp() {
+        MockitoAnnotations.initMocks(this)
+
+        underTest = WalletAutoAddable(walletController)
+    }
+
+    @Test
+    fun strategyIfNotAdded() {
+        assertThat(underTest.autoAddTracking).isEqualTo(AutoAddTracking.IfNotAdded(SPEC))
+    }
+
+    @Test
+    fun walletPositionNull_noSignal() = runTest {
+        whenever(walletController.getWalletPosition()).thenReturn(null)
+
+        val signal by collectLastValue(underTest.autoAddSignal(0))
+
+        assertThat(signal).isNull()
+    }
+
+    @Test
+    fun walletPositionNumber_addedInThatPosition() = runTest {
+        val position = 4
+        whenever(walletController.getWalletPosition()).thenReturn(4)
+
+        val signal by collectLastValue(underTest.autoAddSignal(0))
+
+        assertThat(signal).isEqualTo(AutoAddSignal.Add(SPEC, position))
+    }
+
+    companion object {
+        private val SPEC = TileSpec.create(QuickAccessWalletTile.TILE_SPEC)
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/autoaddable/WorkTileAutoAddableTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/autoaddable/WorkTileAutoAddableTest.kt
new file mode 100644
index 0000000..e9f7c8ab
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/autoaddable/WorkTileAutoAddableTest.kt
@@ -0,0 +1,123 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.pipeline.domain.autoaddable
+
+import android.content.pm.UserInfo
+import android.content.pm.UserInfo.FLAG_FULL
+import android.content.pm.UserInfo.FLAG_MANAGED_PROFILE
+import android.content.pm.UserInfo.FLAG_PRIMARY
+import android.content.pm.UserInfo.FLAG_PROFILE
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.qs.pipeline.domain.model.AutoAddSignal
+import com.android.systemui.qs.pipeline.domain.model.AutoAddTracking
+import com.android.systemui.qs.pipeline.shared.TileSpec
+import com.android.systemui.qs.tiles.WorkModeTile
+import com.android.systemui.settings.FakeUserTracker
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.MockitoAnnotations
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class WorkTileAutoAddableTest : SysuiTestCase() {
+
+    private lateinit var userTracker: FakeUserTracker
+
+    private lateinit var underTest: WorkTileAutoAddable
+
+    @Before
+    fun setup() {
+        MockitoAnnotations.initMocks(this)
+
+        userTracker =
+            FakeUserTracker(
+                _userId = USER_INFO_0.id,
+                _userInfo = USER_INFO_0,
+                _userProfiles = listOf(USER_INFO_0)
+            )
+
+        underTest = WorkTileAutoAddable(userTracker)
+    }
+
+    @Test
+    fun changeInProfiles_hasManagedProfile_sendsAddSignal() = runTest {
+        val signal by collectLastValue(underTest.autoAddSignal(0))
+
+        userTracker.set(listOf(USER_INFO_0, USER_INFO_WORK), selectedUserIndex = 0)
+
+        assertThat(signal).isEqualTo(AutoAddSignal.Add(SPEC))
+    }
+
+    @Test
+    fun changeInProfiles_noManagedProfile_sendsRemoveSignal() = runTest {
+        userTracker.set(listOf(USER_INFO_0, USER_INFO_WORK), selectedUserIndex = 0)
+
+        val signal by collectLastValue(underTest.autoAddSignal(0))
+
+        userTracker.set(listOf(USER_INFO_0), selectedUserIndex = 0)
+
+        assertThat(signal).isEqualTo(AutoAddSignal.Remove(SPEC))
+    }
+
+    @Test
+    fun startingWithManagedProfile_sendsAddSignal() = runTest {
+        userTracker.set(listOf(USER_INFO_0, USER_INFO_WORK), selectedUserIndex = 0)
+
+        val signal by collectLastValue(underTest.autoAddSignal(0))
+
+        assertThat(signal).isEqualTo(AutoAddSignal.Add(SPEC))
+    }
+
+    @Test
+    fun userChangeToUserWithProfile_noSignalForOriginalUser() = runTest {
+        val signal by collectLastValue(underTest.autoAddSignal(0))
+
+        userTracker.set(listOf(USER_INFO_1, USER_INFO_WORK), selectedUserIndex = 0)
+
+        assertThat(signal).isNotEqualTo(AutoAddSignal.Add(SPEC))
+    }
+
+    @Test
+    fun userChangeToUserWithoutProfile_noSignalForOriginalUser() = runTest {
+        userTracker.set(listOf(USER_INFO_0, USER_INFO_WORK), selectedUserIndex = 0)
+        val signal by collectLastValue(underTest.autoAddSignal(0))
+
+        userTracker.set(listOf(USER_INFO_1), selectedUserIndex = 0)
+
+        assertThat(signal).isNotEqualTo(AutoAddSignal.Remove(SPEC))
+    }
+
+    @Test
+    fun strategyAlways() {
+        assertThat(underTest.autoAddTracking).isEqualTo(AutoAddTracking.Always)
+    }
+
+    companion object {
+        private val SPEC = TileSpec.create(WorkModeTile.TILE_SPEC)
+        private val USER_INFO_0 = UserInfo(0, "", FLAG_PRIMARY or FLAG_FULL)
+        private val USER_INFO_1 = UserInfo(1, "", FLAG_FULL)
+        private val USER_INFO_WORK = UserInfo(10, "", FLAG_PROFILE or FLAG_MANAGED_PROFILE)
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/interactor/AutoAddInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/interactor/AutoAddInteractorTest.kt
new file mode 100644
index 0000000..f924b35
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/interactor/AutoAddInteractorTest.kt
@@ -0,0 +1,201 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.pipeline.domain.interactor
+
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.dump.DumpManager
+import com.android.systemui.qs.pipeline.data.repository.FakeAutoAddRepository
+import com.android.systemui.qs.pipeline.domain.autoaddable.FakeAutoAddable
+import com.android.systemui.qs.pipeline.domain.model.AutoAddTracking
+import com.android.systemui.qs.pipeline.domain.model.AutoAddable
+import com.android.systemui.qs.pipeline.shared.TileSpec
+import com.android.systemui.qs.pipeline.shared.logging.QSPipelineLogger
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.whenever
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.anyInt
+import org.mockito.Mock
+import org.mockito.Mockito.inOrder
+import org.mockito.Mockito.never
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+@OptIn(ExperimentalCoroutinesApi::class)
+class AutoAddInteractorTest : SysuiTestCase() {
+    private val testScope = TestScope()
+
+    private val autoAddRepository = FakeAutoAddRepository()
+
+    @Mock private lateinit var dumpManager: DumpManager
+    @Mock private lateinit var currentTilesInteractor: CurrentTilesInteractor
+    @Mock private lateinit var logger: QSPipelineLogger
+    private lateinit var underTest: AutoAddInteractor
+
+    @Before
+    fun setUp() {
+        MockitoAnnotations.initMocks(this)
+
+        whenever(currentTilesInteractor.userId).thenReturn(MutableStateFlow(USER))
+    }
+
+    @Test
+    fun autoAddable_alwaysTrack_addSignal_tileAddedAndMarked() =
+        testScope.runTest {
+            val fakeAutoAddable = FakeAutoAddable(SPEC, AutoAddTracking.Always)
+            val autoAddedTiles by collectLastValue(autoAddRepository.autoAddedTiles(USER))
+
+            underTest = createInteractor(setOf(fakeAutoAddable))
+
+            val position = 3
+            fakeAutoAddable.sendAddSignal(USER, position)
+            runCurrent()
+
+            verify(currentTilesInteractor).addTile(SPEC, position)
+            assertThat(autoAddedTiles).contains(SPEC)
+        }
+
+    @Test
+    fun autoAddable_alwaysTrack_addThenRemoveSignal_tileAddedAndRemoved() =
+        testScope.runTest {
+            val fakeAutoAddable = FakeAutoAddable(SPEC, AutoAddTracking.Always)
+            val autoAddedTiles by collectLastValue(autoAddRepository.autoAddedTiles(USER))
+
+            underTest = createInteractor(setOf(fakeAutoAddable))
+
+            val position = 3
+            fakeAutoAddable.sendAddSignal(USER, position)
+            runCurrent()
+            fakeAutoAddable.sendRemoveSignal(USER)
+            runCurrent()
+
+            val inOrder = inOrder(currentTilesInteractor)
+            inOrder.verify(currentTilesInteractor).addTile(SPEC, position)
+            inOrder.verify(currentTilesInteractor).removeTiles(setOf(SPEC))
+            assertThat(autoAddedTiles).doesNotContain(SPEC)
+        }
+
+    @Test
+    fun autoAddable_alwaysTrack_addSignalWhenAddedPreviously_noop() =
+        testScope.runTest {
+            val fakeAutoAddable = FakeAutoAddable(SPEC, AutoAddTracking.Always)
+            autoAddRepository.markTileAdded(USER, SPEC)
+            runCurrent()
+
+            underTest = createInteractor(setOf(fakeAutoAddable))
+
+            val position = 3
+            fakeAutoAddable.sendAddSignal(USER, position)
+            runCurrent()
+
+            verify(currentTilesInteractor, never()).addTile(SPEC, position)
+        }
+
+    @Test
+    fun autoAddable_disabled_noInteractionsWithCurrentTilesInteractor() =
+        testScope.runTest {
+            val fakeAutoAddable = FakeAutoAddable(SPEC, AutoAddTracking.Disabled)
+            val autoAddedTiles by collectLastValue(autoAddRepository.autoAddedTiles(USER))
+
+            underTest = createInteractor(setOf(fakeAutoAddable))
+
+            val position = 3
+            fakeAutoAddable.sendAddSignal(USER, position)
+            runCurrent()
+            fakeAutoAddable.sendRemoveSignal(USER)
+            runCurrent()
+
+            verify(currentTilesInteractor, never()).addTile(any(), anyInt())
+            verify(currentTilesInteractor, never()).removeTiles(any())
+            assertThat(autoAddedTiles).doesNotContain(SPEC)
+        }
+
+    @Test
+    fun autoAddable_trackIfNotAdded_removeSignal_noop() =
+        testScope.runTest {
+            val fakeAutoAddable = FakeAutoAddable(SPEC, AutoAddTracking.IfNotAdded(SPEC))
+            runCurrent()
+
+            underTest = createInteractor(setOf(fakeAutoAddable))
+
+            fakeAutoAddable.sendRemoveSignal(USER)
+            runCurrent()
+
+            verify(currentTilesInteractor, never()).addTile(any(), anyInt())
+            verify(currentTilesInteractor, never()).removeTiles(any())
+        }
+
+    @Test
+    fun autoAddable_trackIfNotAdded_addSignalWhenPreviouslyAdded_noop() =
+        testScope.runTest {
+            val fakeAutoAddable = FakeAutoAddable(SPEC, AutoAddTracking.IfNotAdded(SPEC))
+            autoAddRepository.markTileAdded(USER, SPEC)
+            runCurrent()
+
+            underTest = createInteractor(setOf(fakeAutoAddable))
+
+            fakeAutoAddable.sendAddSignal(USER)
+            runCurrent()
+
+            verify(currentTilesInteractor, never()).addTile(any(), anyInt())
+            verify(currentTilesInteractor, never()).removeTiles(any())
+        }
+
+    @Test
+    fun autoAddable_trackIfNotAdded_addSignal_addedAndMarked() =
+        testScope.runTest {
+            val fakeAutoAddable = FakeAutoAddable(SPEC, AutoAddTracking.IfNotAdded(SPEC))
+            val autoAddedTiles by collectLastValue(autoAddRepository.autoAddedTiles(USER))
+
+            underTest = createInteractor(setOf(fakeAutoAddable))
+
+            val position = 3
+            fakeAutoAddable.sendAddSignal(USER, position)
+            runCurrent()
+
+            verify(currentTilesInteractor).addTile(SPEC, position)
+            assertThat(autoAddedTiles).contains(SPEC)
+        }
+
+    private fun createInteractor(autoAddables: Set<AutoAddable>): AutoAddInteractor {
+        return AutoAddInteractor(
+                autoAddables,
+                autoAddRepository,
+                dumpManager,
+                logger,
+                testScope.backgroundScope
+            )
+            .apply { init(currentTilesInteractor) }
+    }
+
+    companion object {
+        private val SPEC = TileSpec.create("spec")
+        private val USER = 10
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractorImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractorImplTest.kt
index e7ad489..30cea2d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractorImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractorImplTest.kt
@@ -100,6 +100,7 @@
         MockitoAnnotations.initMocks(this)
 
         featureFlags.set(Flags.QS_PIPELINE_NEW_HOST, true)
+        featureFlags.set(Flags.QS_PIPELINE_AUTO_ADD, true)
 
         userRepository.setUserInfos(listOf(USER_INFO_0, USER_INFO_1))
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSTileViewImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSTileViewImplTest.kt
index 28aeba4..3c66772 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSTileViewImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSTileViewImplTest.kt
@@ -22,6 +22,7 @@
 import android.testing.AndroidTestingRunner
 import android.testing.TestableLooper
 import android.text.TextUtils
+import android.view.ContextThemeWrapper
 import android.view.View
 import android.view.accessibility.AccessibilityNodeInfo
 import android.widget.TextView
@@ -386,7 +387,11 @@
         context: Context,
         icon: QSIconView,
         collapsed: Boolean
-    ) : QSTileViewImpl(context, icon, collapsed) {
+    ) : QSTileViewImpl(
+            ContextThemeWrapper(context, R.style.Theme_SystemUI_QuickSettings),
+            icon,
+            collapsed
+    ) {
         fun changeState(state: QSTile.State) {
             handleStateChanged(state)
         }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/DndTileTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/DndTileTest.kt
index f0e4e3a..77a4436 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/DndTileTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/DndTileTest.kt
@@ -25,6 +25,7 @@
 import android.provider.Settings.Global.ZEN_MODE_OFF
 import android.testing.AndroidTestingRunner
 import android.testing.TestableLooper
+import android.view.ContextThemeWrapper
 import android.view.View
 import androidx.test.filters.SmallTest
 import com.android.internal.logging.MetricsLogger
@@ -110,7 +111,9 @@
 
         whenever(qsHost.userId).thenReturn(DEFAULT_USER)
 
-        val wrappedContext = object : ContextWrapper(context) {
+        val wrappedContext = object : ContextWrapper(
+                ContextThemeWrapper(context, R.style.Theme_SystemUI_QuickSettings)
+        ) {
             override fun getSharedPreferences(file: File?, mode: Int): SharedPreferences {
                 return sharedPreferences
             }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModelTest.kt
index 05a1699..c85c8ba 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModelTest.kt
@@ -27,7 +27,6 @@
 import com.android.systemui.scene.shared.model.SceneModel
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.test.TestScope
 import kotlinx.coroutines.test.runCurrent
 import kotlinx.coroutines.test.runTest
 import org.junit.Test
@@ -39,8 +38,8 @@
 @RunWith(JUnit4::class)
 class QuickSettingsSceneViewModelTest : SysuiTestCase() {
 
-    private val testScope = TestScope()
-    private val utils = SceneTestUtils(this, testScope)
+    private val utils = SceneTestUtils(this)
+    private val testScope = utils.testScope
     private val sceneInteractor = utils.sceneInteractor()
     private val authenticationInteractor =
         utils.authenticationInteractor(
@@ -70,8 +69,10 @@
     fun onContentClicked_deviceUnlocked_switchesToGone() =
         testScope.runTest {
             val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_1))
-            authenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.Pin(1234))
-            authenticationInteractor.unlockDevice()
+            utils.authenticationRepository.setAuthenticationMethod(
+                AuthenticationMethodModel.Pin(1234)
+            )
+            utils.authenticationRepository.setUnlocked(true)
             runCurrent()
 
             underTest.onContentClicked()
@@ -83,8 +84,10 @@
     fun onContentClicked_deviceLockedSecurely_switchesToBouncer() =
         testScope.runTest {
             val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_1))
-            authenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.Pin(1234))
-            authenticationInteractor.lockDevice()
+            utils.authenticationRepository.setAuthenticationMethod(
+                AuthenticationMethodModel.Pin(1234)
+            )
+            utils.authenticationRepository.setUnlocked(false)
             runCurrent()
 
             underTest.onContentClicked()
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 ef7c7bc..9188293 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
@@ -80,6 +80,7 @@
 import com.android.systemui.R;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.biometrics.AuthController;
+import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor;
 import com.android.systemui.classifier.FalsingCollectorFake;
 import com.android.systemui.classifier.FalsingManagerFake;
 import com.android.systemui.common.ui.view.LongPressHandlingView;
@@ -91,7 +92,6 @@
 import com.android.systemui.keyguard.KeyguardUnlockAnimationController;
 import com.android.systemui.keyguard.KeyguardViewConfigurator;
 import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository;
-import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor;
 import com.android.systemui.keyguard.domain.interactor.KeyguardBottomAreaInteractor;
 import com.android.systemui.keyguard.domain.interactor.KeyguardFaceAuthInteractor;
 import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor;
@@ -629,7 +629,7 @@
                 mHeadsUpManager);
         mNotificationPanelViewController.setTrackingStartedListener(() -> {});
         mNotificationPanelViewController.setOpenCloseListener(
-                new NotificationPanelViewController.OpenCloseListener() {
+                new OpenCloseListener() {
                     @Override
                     public void onClosingFinished() {}
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/ShadeControllerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/ShadeControllerImplTest.kt
index 00a0567..729c4a9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/ShadeControllerImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/ShadeControllerImplTest.kt
@@ -56,7 +56,7 @@
     @Mock private lateinit var windowManager: WindowManager
     @Mock private lateinit var assistManager: AssistManager
     @Mock private lateinit var gutsManager: NotificationGutsManager
-    @Mock private lateinit var notificationPanelViewController: NotificationPanelViewController
+    @Mock private lateinit var shadeViewController: ShadeViewController
     @Mock private lateinit var nswvc: NotificationShadeWindowViewController
     @Mock private lateinit var display: Display
 
@@ -82,7 +82,7 @@
                 Lazy { gutsManager },
             )
         shadeController.setNotificationShadeWindowViewController(nswvc)
-        shadeController.setNotificationPanelViewController(notificationPanelViewController)
+        shadeController.setShadeViewController(shadeViewController)
     }
 
     @Test
@@ -91,9 +91,9 @@
 
         // Trying to open it does nothing.
         shadeController.animateExpandShade()
-        verify(notificationPanelViewController, never()).expandToNotifications()
+        verify(shadeViewController, never()).expandToNotifications()
         shadeController.animateExpandQs()
-        verify(notificationPanelViewController, never()).expand(ArgumentMatchers.anyBoolean())
+        verify(shadeViewController, never()).expand(ArgumentMatchers.anyBoolean())
     }
 
     @Test
@@ -102,15 +102,15 @@
 
         // Can now be opened.
         shadeController.animateExpandShade()
-        verify(notificationPanelViewController).expandToNotifications()
+        verify(shadeViewController).expandToNotifications()
         shadeController.animateExpandQs()
-        verify(notificationPanelViewController).expandToQs()
+        verify(shadeViewController).expandToQs()
     }
 
     @Test
     fun cancelExpansionAndCollapseShade_callsCancelCurrentTouch() {
         // GIVEN the shade is tracking a touch
-        whenever(notificationPanelViewController.isTracking).thenReturn(true)
+        whenever(shadeViewController.isTracking).thenReturn(true)
 
         // WHEN cancelExpansionAndCollapseShade is called
         shadeController.cancelExpansionAndCollapseShade()
@@ -122,7 +122,7 @@
     @Test
     fun cancelExpansionAndCollapseShade_doesNotCallAnimateCollapseShade_whenCollapsed() {
         // GIVEN the shade is tracking a touch
-        whenever(notificationPanelViewController.isTracking).thenReturn(false)
+        whenever(shadeViewController.isTracking).thenReturn(false)
 
         // WHEN cancelExpansionAndCollapseShade is called
         shadeController.cancelExpansionAndCollapseShade()
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 2da2e92..f542ab0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/ShadeHeaderControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/ShadeHeaderControllerTest.kt
@@ -237,11 +237,22 @@
         whenever(mShadeCarrierGroupController.isSingleCarrier).thenReturn(false)
 
         makeShadeVisible()
+        shadeHeaderController.qsExpandedFraction = 1.0f
 
         verify(statusIcons).addIgnoredSlots(carrierIconSlots)
     }
 
     @Test
+    fun dualCarrier_enablesCarrierIconsInStatusIcons_qsExpanded() {
+        whenever(mShadeCarrierGroupController.isSingleCarrier).thenReturn(false)
+
+        makeShadeVisible()
+        shadeHeaderController.qsExpandedFraction = 0.0f
+
+        verify(statusIcons, times(2)).removeIgnoredSlots(carrierIconSlots)
+    }
+
+    @Test
     fun disableQS_notDisabled_visible() {
         makeShadeVisible()
         shadeHeaderController.disable(0, 0, false)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/carrier/ShadeCarrierTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/carrier/ShadeCarrierTest.java
index 4461310..dae9c97 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/carrier/ShadeCarrierTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/carrier/ShadeCarrierTest.java
@@ -20,8 +20,10 @@
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
 
+import android.content.Context;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
+import android.view.ContextThemeWrapper;
 import android.view.LayoutInflater;
 import android.view.View;
 import android.widget.TextView;
@@ -48,7 +50,9 @@
     @Before
     public void setUp() throws Exception {
         mTestableLooper = TestableLooper.get(this);
-        LayoutInflater inflater = LayoutInflater.from(mContext);
+        Context themedContext =
+                new ContextThemeWrapper(mContext, R.style.Theme_SystemUI_QuickSettings);
+        LayoutInflater inflater = LayoutInflater.from(themedContext);
         mContext.ensureTestableResources();
         mTestableLooper.runWithLooper(() ->
                 mShadeCarrier = (ShadeCarrier) inflater.inflate(R.layout.shade_carrier, null));
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelTest.kt
index f8e1a9d..5d2d192 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelTest.kt
@@ -27,7 +27,6 @@
 import com.android.systemui.scene.shared.model.SceneModel
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.test.TestScope
 import kotlinx.coroutines.test.runCurrent
 import kotlinx.coroutines.test.runTest
 import org.junit.Test
@@ -39,8 +38,8 @@
 @RunWith(JUnit4::class)
 class ShadeSceneViewModelTest : SysuiTestCase() {
 
-    private val testScope = TestScope()
-    private val utils = SceneTestUtils(this, testScope)
+    private val utils = SceneTestUtils(this)
+    private val testScope = utils.testScope
     private val sceneInteractor = utils.sceneInteractor()
     private val authenticationInteractor =
         utils.authenticationInteractor(
@@ -71,8 +70,10 @@
     fun upTransitionSceneKey_deviceLocked_lockScreen() =
         testScope.runTest {
             val upTransitionSceneKey by collectLastValue(underTest.upDestinationSceneKey)
-            authenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.Pin(1234))
-            authenticationInteractor.lockDevice()
+            utils.authenticationRepository.setAuthenticationMethod(
+                AuthenticationMethodModel.Pin(1234)
+            )
+            utils.authenticationRepository.setUnlocked(false)
 
             assertThat(upTransitionSceneKey).isEqualTo(SceneKey.Lockscreen)
         }
@@ -81,8 +82,10 @@
     fun upTransitionSceneKey_deviceUnlocked_gone() =
         testScope.runTest {
             val upTransitionSceneKey by collectLastValue(underTest.upDestinationSceneKey)
-            authenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.Pin(1234))
-            authenticationInteractor.unlockDevice()
+            utils.authenticationRepository.setAuthenticationMethod(
+                AuthenticationMethodModel.Pin(1234)
+            )
+            utils.authenticationRepository.setUnlocked(true)
 
             assertThat(upTransitionSceneKey).isEqualTo(SceneKey.Gone)
         }
@@ -91,8 +94,10 @@
     fun onContentClicked_deviceUnlocked_switchesToGone() =
         testScope.runTest {
             val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_1))
-            authenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.Pin(1234))
-            authenticationInteractor.unlockDevice()
+            utils.authenticationRepository.setAuthenticationMethod(
+                AuthenticationMethodModel.Pin(1234)
+            )
+            utils.authenticationRepository.setUnlocked(true)
             runCurrent()
 
             underTest.onContentClicked()
@@ -104,8 +109,10 @@
     fun onContentClicked_deviceLockedSecurely_switchesToBouncer() =
         testScope.runTest {
             val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_1))
-            authenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.Pin(1234))
-            authenticationInteractor.lockDevice()
+            utils.authenticationRepository.setAuthenticationMethod(
+                AuthenticationMethodModel.Pin(1234)
+            )
+            utils.authenticationRepository.setUnlocked(false)
             runCurrent()
 
             underTest.onContentClicked()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/CommandQueueTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/CommandQueueTest.java
index 385d556..1643e17 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/CommandQueueTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/CommandQueueTest.java
@@ -16,6 +16,7 @@
 
 import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FACE;
 import static android.inputmethodservice.InputMethodService.BACK_DISPOSITION_DEFAULT;
+import static android.inputmethodservice.InputMethodService.IME_INVISIBLE;
 import static android.view.Display.DEFAULT_DISPLAY;
 import static android.view.WindowInsetsController.BEHAVIOR_DEFAULT;
 
@@ -200,7 +201,7 @@
 
         mCommandQueue.setImeWindowStatus(SECONDARY_DISPLAY, null, 1, 2, true);
         waitForIdleSync();
-        verify(mCallbacks).setImeWindowStatus(eq(DEFAULT_DISPLAY), eq(null), eq(0),
+        verify(mCallbacks).setImeWindowStatus(eq(DEFAULT_DISPLAY), eq(null), eq(IME_INVISIBLE),
                 eq(BACK_DISPOSITION_DEFAULT), eq(false));
         verify(mCallbacks).setImeWindowStatus(
                 eq(SECONDARY_DISPLAY), eq(null), eq(1), eq(2), eq(true));
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/events/SystemStatusAnimationSchedulerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/events/SystemStatusAnimationSchedulerImplTest.kt
index 914301f..2af0ceb 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/events/SystemStatusAnimationSchedulerImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/events/SystemStatusAnimationSchedulerImplTest.kt
@@ -69,6 +69,8 @@
 
     @Mock private lateinit var listener: SystemStatusAnimationCallback
 
+    @Mock private lateinit var logger: SystemStatusAnimationSchedulerLogger
+
     private lateinit var systemClock: FakeSystemClock
     private lateinit var chipAnimationController: SystemEventChipAnimationController
     private lateinit var systemStatusAnimationScheduler: SystemStatusAnimationScheduler
@@ -538,7 +540,8 @@
                 statusBarWindowController,
                 dumpManager,
                 systemClock,
-                CoroutineScope(StandardTestDispatcher(testScope.testScheduler))
+                CoroutineScope(StandardTestDispatcher(testScope.testScheduler)),
+                logger
             )
         // add a mock listener
         systemStatusAnimationScheduler.addCallback(listener)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinatorLoggerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinatorLoggerTest.kt
index a37c386..902dd51 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinatorLoggerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinatorLoggerTest.kt
@@ -20,8 +20,8 @@
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.log.LogBuffer
-import com.android.systemui.log.LogLevel
 import com.android.systemui.log.LogcatEchoTracker
+import com.android.systemui.log.core.LogLevel
 import com.android.systemui.statusbar.StatusBarState
 import com.google.common.truth.Truth.assertThat
 import org.junit.Before
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/StackStateLoggerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/StackStateLoggerTest.kt
index 7707a7e..47c5e5b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/StackStateLoggerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/StackStateLoggerTest.kt
@@ -20,8 +20,8 @@
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.log.LogBuffer
-import com.android.systemui.log.LogLevel
 import com.android.systemui.log.LogcatEchoTracker
+import com.android.systemui.log.core.LogLevel
 import com.android.systemui.statusbar.notification.stack.StackStateLogger
 import com.google.common.truth.Truth
 import org.junit.Before
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/AutoTileManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/AutoTileManagerTest.java
index e680a4e..e4a2236 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/AutoTileManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/AutoTileManagerTest.java
@@ -34,6 +34,7 @@
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
 import static org.mockito.Mockito.when;
 
 import android.content.ComponentName;
@@ -51,6 +52,7 @@
 
 import com.android.systemui.R;
 import com.android.systemui.SysuiTestCase;
+import com.android.systemui.dagger.NightDisplayListenerModule;
 import com.android.systemui.plugins.qs.QSTile;
 import com.android.systemui.qs.AutoAddTracker;
 import com.android.systemui.qs.QSHost;
@@ -111,6 +113,8 @@
     @Mock private DataSaverController mDataSaverController;
     @Mock private ManagedProfileController mManagedProfileController;
     @Mock private NightDisplayListener mNightDisplayListener;
+    @Mock(answer = Answers.RETURNS_SELF)
+    private NightDisplayListenerModule.Builder mNightDisplayListenerBuilder;
     @Mock private ReduceBrightColorsController mReduceBrightColorsController;
     @Mock private DeviceControlsController mDeviceControlsController;
     @Mock private WalletController mWalletController;
@@ -151,6 +155,7 @@
                 .thenReturn(TEST_CUSTOM_SAFETY_PKG);
         Context context = Mockito.spy(mContext);
         when(context.getPackageManager()).thenReturn(mPackageManager);
+        when(mNightDisplayListenerBuilder.build()).thenReturn(mNightDisplayListener);
 
         mAutoTileManager = createAutoTileManager(context);
         mAutoTileManager.init();
@@ -167,7 +172,7 @@
             HotspotController hotspotController,
             DataSaverController dataSaverController,
             ManagedProfileController managedProfileController,
-            NightDisplayListener nightDisplayListener,
+            NightDisplayListenerModule.Builder nightDisplayListenerBuilder,
             CastController castController,
             ReduceBrightColorsController reduceBrightColorsController,
             DeviceControlsController deviceControlsController,
@@ -180,7 +185,7 @@
                 hotspotController,
                 dataSaverController,
                 managedProfileController,
-                nightDisplayListener,
+                mNightDisplayListenerBuilder,
                 castController,
                 reduceBrightColorsController,
                 deviceControlsController,
@@ -191,7 +196,7 @@
 
     private AutoTileManager createAutoTileManager(Context context) {
         return createAutoTileManager(context, mAutoAddTrackerBuilder, mHotspotController,
-                mDataSaverController, mManagedProfileController, mNightDisplayListener,
+                mDataSaverController, mManagedProfileController, mNightDisplayListenerBuilder,
                 mCastController, mReduceBrightColorsController, mDeviceControlsController,
                 mWalletController, mSafetyController, mIsReduceBrightColorsAvailable);
     }
@@ -204,7 +209,7 @@
         HotspotController hC = mock(HotspotController.class);
         DataSaverController dSC = mock(DataSaverController.class);
         ManagedProfileController mPC = mock(ManagedProfileController.class);
-        NightDisplayListener nDS = mock(NightDisplayListener.class);
+        NightDisplayListenerModule.Builder nDSB = mock(NightDisplayListenerModule.Builder.class);
         CastController cC = mock(CastController.class);
         ReduceBrightColorsController rBC = mock(ReduceBrightColorsController.class);
         DeviceControlsController dCC = mock(DeviceControlsController.class);
@@ -212,14 +217,14 @@
         SafetyController sC = mock(SafetyController.class);
 
         AutoTileManager manager =
-                createAutoTileManager(mock(Context.class), builder, hC, dSC, mPC, nDS, cC, rBC,
+                createAutoTileManager(mock(Context.class), builder, hC, dSC, mPC, nDSB, cC, rBC,
                         dCC, wC, sC, true);
 
         verify(tracker, never()).initialize();
         verify(hC, never()).addCallback(any());
         verify(dSC, never()).addCallback(any());
         verify(mPC, never()).addCallback(any());
-        verify(nDS, never()).setCallback(any());
+        verifyNoMoreInteractions(nDSB);
         verify(cC, never()).addCallback(any());
         verify(rBC, never()).addCallback(any());
         verify(dCC, never()).setCallback(any());
@@ -615,6 +620,15 @@
         createAutoTileManager(mContext).destroy();
     }
 
+    @Test
+    public void testUserChange_newNightDisplayListenerCreated() {
+        UserHandle newUser = UserHandle.of(1000);
+        mAutoTileManager.changeUser(newUser);
+        InOrder inOrder = inOrder(mNightDisplayListenerBuilder);
+        inOrder.verify(mNightDisplayListenerBuilder).setUser(newUser.getIdentifier());
+        inOrder.verify(mNightDisplayListenerBuilder).build();
+    }
+
     // Will only notify if it's listening
     private void changeValue(String key, int value) {
         mSecureSettings.putIntForUser(key, value, USER);
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 1ffffe4..88d8dfc 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
@@ -450,7 +450,7 @@
                 () -> mAssistManager,
                 () -> mNotificationGutsManager
         ));
-        mShadeController.setNotificationPanelViewController(mNotificationPanelViewController);
+        mShadeController.setShadeViewController(mNotificationPanelViewController);
         mShadeController.setNotificationShadeWindowViewController(
                 mNotificationShadeWindowViewController);
         mShadeController.setNotificationPresenter(mNotificationPresenter);
@@ -490,9 +490,11 @@
                 mMetricsLogger,
                 mShadeLogger,
                 mUiBgExecutor,
+                mNotificationPanelViewController,
                 mNotificationMediaManager,
                 mLockscreenUserManager,
                 mRemoteInputManager,
+                mQuickSettingsController,
                 mUserSwitcherController,
                 mBatteryController,
                 mColorExtractor,
@@ -587,8 +589,6 @@
         // TODO: we should be able to call mCentralSurfaces.start() and have all the below values
         // initialized automatically and make NPVC private.
         mCentralSurfaces.mNotificationShadeWindowView = mNotificationShadeWindowView;
-        mCentralSurfaces.mShadeSurface = mNotificationPanelViewController;
-        mCentralSurfaces.mQsController = mQuickSettingsController;
         mCentralSurfaces.mDozeScrimController = mDozeScrimController;
         mCentralSurfaces.mPresenter = mNotificationPresenter;
         mCentralSurfaces.mKeyguardIndicationController = mKeyguardIndicationController;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardBypassControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardBypassControllerTest.kt
index 3e90ed9..4aac841 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardBypassControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardBypassControllerTest.kt
@@ -42,6 +42,8 @@
 import org.mockito.ArgumentCaptor
 import org.mockito.Captor
 import org.mockito.Mock
+import org.mockito.Mockito.anyBoolean
+import org.mockito.Mockito.mock
 import org.mockito.Mockito.never
 import org.mockito.Mockito.reset
 import org.mockito.Mockito.verify
@@ -135,6 +137,19 @@
     }
 
     @Test
+    fun onFaceAuthEnabledChanged_notifiesBypassEnabledListeners() {
+        initKeyguardBypassController()
+        val bypassListener = mock(KeyguardBypassController.OnBypassStateChangedListener::class.java)
+        val callback = ArgumentCaptor.forClass(KeyguardStateController.Callback::class.java)
+
+        keyguardBypassController.registerOnBypassStateChangedListener(bypassListener)
+        verify(keyguardStateController).addCallback(callback.capture())
+
+        callback.value.onFaceAuthEnabledChanged()
+        verify(bypassListener).onBypassStateChanged(anyBoolean())
+    }
+
+    @Test
     fun configDevicePostureClosed_matchState_isPostureAllowedForFaceAuth_returnTrue() {
         defaultConfigPostureClosed()
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicyTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicyTest.kt
index 6b18169..85fbef0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicyTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicyTest.kt
@@ -27,6 +27,8 @@
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.broadcast.BroadcastDispatcher
+import com.android.systemui.display.domain.interactor.ConnectedDisplayInteractor
+import com.android.systemui.display.domain.interactor.ConnectedDisplayInteractor.State
 import com.android.systemui.privacy.PrivacyItemController
 import com.android.systemui.privacy.logging.PrivacyLogger
 import com.android.systemui.screenrecord.RecordingController
@@ -46,9 +48,17 @@
 import com.android.systemui.statusbar.policy.ZenModeController
 import com.android.systemui.util.RingerModeTracker
 import com.android.systemui.util.concurrency.FakeExecutor
+import com.android.systemui.util.kotlin.JavaAdapter
 import com.android.systemui.util.mockito.capture
 import com.android.systemui.util.time.DateFormatUtil
 import com.android.systemui.util.time.FakeSystemClock
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableSharedFlow
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.UnconfinedTestDispatcher
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -57,6 +67,8 @@
 import org.mockito.Captor
 import org.mockito.Mock
 import org.mockito.Mockito.anyInt
+import org.mockito.Mockito.clearInvocations
+import org.mockito.Mockito.inOrder
 import org.mockito.Mockito.never
 import org.mockito.Mockito.verify
 import org.mockito.Mockito.`when` as whenever
@@ -64,11 +76,13 @@
 
 @RunWith(AndroidTestingRunner::class)
 @RunWithLooper
+@OptIn(ExperimentalCoroutinesApi::class)
 @SmallTest
 class PhoneStatusBarPolicyTest : SysuiTestCase() {
 
     companion object {
         private const val ALARM_SLOT = "alarm"
+        private const val CONNECTED_DISPLAY_SLOT = "connected_display"
     }
 
     @Mock private lateinit var iconController: StatusBarIconController
@@ -102,6 +116,9 @@
     private lateinit var alarmCallbackCaptor:
         ArgumentCaptor<NextAlarmController.NextAlarmChangeCallback>
 
+    private val testScope = TestScope(UnconfinedTestDispatcher())
+    private val fakeConnectedDisplayStateProvider = FakeConnectedDisplayStateProvider()
+
     private lateinit var executor: FakeExecutor
     private lateinit var statusBarPolicy: PhoneStatusBarPolicy
     private lateinit var testableLooper: TestableLooper
@@ -164,6 +181,57 @@
         verify(iconController).setIconVisibility(ALARM_SLOT, true)
     }
 
+    @Test
+    fun connectedDisplay_connected_iconShown() =
+        testScope.runTest {
+            statusBarPolicy.init()
+            clearInvocations(iconController)
+
+            fakeConnectedDisplayStateProvider.emit(State.CONNECTED)
+            runCurrent()
+
+            verify(iconController).setIconVisibility(CONNECTED_DISPLAY_SLOT, true)
+        }
+
+    @Test
+    fun connectedDisplay_disconnected_iconHidden() =
+        testScope.runTest {
+            statusBarPolicy.init()
+            clearInvocations(iconController)
+
+            fakeConnectedDisplayStateProvider.emit(State.DISCONNECTED)
+
+            verify(iconController).setIconVisibility(CONNECTED_DISPLAY_SLOT, false)
+        }
+
+    @Test
+    fun connectedDisplay_disconnectedThenConnected_iconShown() =
+        testScope.runTest {
+            statusBarPolicy.init()
+            clearInvocations(iconController)
+
+            fakeConnectedDisplayStateProvider.emit(State.CONNECTED)
+            fakeConnectedDisplayStateProvider.emit(State.DISCONNECTED)
+            fakeConnectedDisplayStateProvider.emit(State.CONNECTED)
+
+            inOrder(iconController).apply {
+                verify(iconController).setIconVisibility(CONNECTED_DISPLAY_SLOT, true)
+                verify(iconController).setIconVisibility(CONNECTED_DISPLAY_SLOT, false)
+                verify(iconController).setIconVisibility(CONNECTED_DISPLAY_SLOT, true)
+            }
+        }
+
+    @Test
+    fun connectedDisplay_connectSecureDisplay_iconShown() =
+        testScope.runTest {
+            statusBarPolicy.init()
+            clearInvocations(iconController)
+
+            fakeConnectedDisplayStateProvider.emit(State.CONNECTED_SECURE)
+
+            verify(iconController).setIconVisibility(CONNECTED_DISPLAY_SLOT, true)
+        }
+
     private fun createAlarmInfo(): AlarmManager.AlarmClockInfo {
         return AlarmManager.AlarmClockInfo(10L, null)
     }
@@ -200,7 +268,16 @@
             dateFormatUtil,
             ringerModeTracker,
             privacyItemController,
-            privacyLogger
+            privacyLogger,
+            fakeConnectedDisplayStateProvider,
+            JavaAdapter(testScope.backgroundScope)
         )
     }
+
+    private class FakeConnectedDisplayStateProvider : ConnectedDisplayInteractor {
+        private val flow = MutableSharedFlow<State>()
+        suspend fun emit(value: State) = flow.emit(value)
+        override val connectedDisplayState: Flow<State>
+            get() = flow
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
index 548e1b5..c7143de 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
@@ -737,6 +737,16 @@
     }
 
     @Test
+    public void testResetBouncerAnimatingAway() {
+        reset(mPrimaryBouncerInteractor);
+        when(mPrimaryBouncerInteractor.isAnimatingAway()).thenReturn(true);
+
+        mStatusBarKeyguardViewManager.reset(true);
+
+        verify(mPrimaryBouncerInteractor, never()).hide();
+    }
+
+    @Test
     public void handleDispatchTouchEvent_alternateBouncerNotVisible() {
         mStatusBarKeyguardViewManager.addCallback(mCallback);
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java
index 9b1d93b..5dcb901 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java
@@ -18,10 +18,6 @@
 
 import static com.android.systemui.shade.ShadeExpansionStateManagerKt.STATE_CLOSED;
 import static com.android.systemui.shade.ShadeExpansionStateManagerKt.STATE_OPEN;
-import static com.android.systemui.statusbar.events.SystemStatusAnimationSchedulerKt.ANIMATING_IN;
-import static com.android.systemui.statusbar.events.SystemStatusAnimationSchedulerKt.ANIMATING_OUT;
-import static com.android.systemui.statusbar.events.SystemStatusAnimationSchedulerKt.IDLE;
-import static com.android.systemui.statusbar.events.SystemStatusAnimationSchedulerKt.RUNNING_CHIP_ANIM;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
@@ -49,7 +45,7 @@
 import android.view.ViewPropertyAnimator;
 import android.widget.FrameLayout;
 
-import androidx.core.animation.Animator;
+import androidx.core.animation.AnimatorTestRule;
 import androidx.test.filters.SmallTest;
 
 import com.android.keyguard.KeyguardUpdateMonitor;
@@ -85,6 +81,7 @@
 import com.android.systemui.util.time.FakeSystemClock;
 
 import org.junit.Before;
+import org.junit.ClassRule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.Mock;
@@ -139,6 +136,8 @@
     private StatusBarWindowStateController mStatusBarWindowStateController;
     @Mock
     private KeyguardUpdateMonitor mKeyguardUpdateMonitor;
+    @ClassRule
+    public static AnimatorTestRule mAnimatorTestRule = new AnimatorTestRule();
 
     private List<StatusBarWindowStateListener> mStatusBarWindowStateListeners = new ArrayList<>();
 
@@ -172,7 +171,6 @@
 
     @Test
     public void testDisableSystemInfo_systemAnimationIdle_doesHide() {
-        when(mAnimationScheduler.getAnimationState()).thenReturn(IDLE);
         CollapsedStatusBarFragment fragment = resumeAndGetFragment();
 
         fragment.disable(DEFAULT_DISPLAY, StatusBarManager.DISABLE_SYSTEM_INFO, 0, false);
@@ -192,24 +190,26 @@
     public void testSystemStatusAnimation_startedDisabled_finishedWithAnimator_showsSystemInfo() {
         // GIVEN the status bar hides the system info via disable flags, while there is no event
         CollapsedStatusBarFragment fragment = resumeAndGetFragment();
-        when(mAnimationScheduler.getAnimationState()).thenReturn(IDLE);
         fragment.disable(DEFAULT_DISPLAY, StatusBarManager.DISABLE_SYSTEM_INFO, 0, false);
         assertEquals(View.INVISIBLE, getEndSideContentView().getVisibility());
 
+        // WHEN the system event animation starts
+        fragment.onSystemEventAnimationBegin().start();
+
+        // THEN the view remains invisible during the animation
+        assertEquals(0f, getEndSideContentView().getAlpha(), 0.01);
+        mAnimatorTestRule.advanceTimeBy(500);
+        assertEquals(0f, getEndSideContentView().getAlpha(), 0.01);
+
         // WHEN the disable flags are cleared during a system event animation
-        when(mAnimationScheduler.getAnimationState()).thenReturn(RUNNING_CHIP_ANIM);
         fragment.disable(DEFAULT_DISPLAY, 0, 0, false);
 
-        // THEN the view is made visible again, but still low alpha
-        assertEquals(View.VISIBLE, getEndSideContentView().getVisibility());
+        // THEN the view remains invisible
         assertEquals(0, getEndSideContentView().getAlpha(), 0.01);
 
         // WHEN the system event animation finishes
-        when(mAnimationScheduler.getAnimationState()).thenReturn(ANIMATING_OUT);
-        Animator anim = fragment.onSystemEventAnimationFinish(false);
-        anim.start();
-        processAllMessages();
-        anim.end();
+        fragment.onSystemEventAnimationFinish(false).start();
+        mAnimatorTestRule.advanceTimeBy(500);
 
         // THEN the system info is full alpha
         assertEquals(1, getEndSideContentView().getAlpha(), 0.01);
@@ -219,20 +219,15 @@
     public void testSystemStatusAnimation_systemInfoDisabled_staysInvisible() {
         // GIVEN the status bar hides the system info via disable flags, while there is no event
         CollapsedStatusBarFragment fragment = resumeAndGetFragment();
-        when(mAnimationScheduler.getAnimationState()).thenReturn(IDLE);
         fragment.disable(DEFAULT_DISPLAY, StatusBarManager.DISABLE_SYSTEM_INFO, 0, false);
         assertEquals(View.INVISIBLE, getEndSideContentView().getVisibility());
 
         // WHEN the system event animation finishes
-        when(mAnimationScheduler.getAnimationState()).thenReturn(ANIMATING_OUT);
-        Animator anim = fragment.onSystemEventAnimationFinish(false);
-        anim.start();
-        processAllMessages();
-        anim.end();
+        fragment.onSystemEventAnimationFinish(false).start();
+        mAnimatorTestRule.advanceTimeBy(500);
 
-        // THEN the system info is at full alpha, but still INVISIBLE (since the disable flag is
-        // still set)
-        assertEquals(1, getEndSideContentView().getAlpha(), 0.01);
+        // THEN the system info remains invisible (since the disable flag is still set)
+        assertEquals(0, getEndSideContentView().getAlpha(), 0.01);
         assertEquals(View.INVISIBLE, getEndSideContentView().getVisibility());
     }
 
@@ -241,15 +236,14 @@
     public void testSystemStatusAnimation_notDisabled_animatesAlphaZero() {
         // GIVEN the status bar is not disabled
         CollapsedStatusBarFragment fragment = resumeAndGetFragment();
-        when(mAnimationScheduler.getAnimationState()).thenReturn(ANIMATING_IN);
-        // WHEN the system event animation begins
-        Animator anim = fragment.onSystemEventAnimationBegin();
-        anim.start();
-        processAllMessages();
-        anim.end();
+        assertEquals(1, getEndSideContentView().getAlpha(), 0.01);
 
-        // THEN the system info is visible but alpha 0
-        assertEquals(View.VISIBLE, getEndSideContentView().getVisibility());
+        // WHEN the system event animation begins
+        fragment.onSystemEventAnimationBegin().start();
+        mAnimatorTestRule.advanceTimeBy(500);
+
+        // THEN the system info is invisible
+        assertEquals(View.INVISIBLE, getEndSideContentView().getVisibility());
         assertEquals(0, getEndSideContentView().getAlpha(), 0.01);
     }
 
@@ -257,25 +251,21 @@
     public void testSystemStatusAnimation_notDisabled_animatesBackToAlphaOne() {
         // GIVEN the status bar is not disabled
         CollapsedStatusBarFragment fragment = resumeAndGetFragment();
-        when(mAnimationScheduler.getAnimationState()).thenReturn(ANIMATING_IN);
-        // WHEN the system event animation begins
-        Animator anim = fragment.onSystemEventAnimationBegin();
-        anim.start();
-        processAllMessages();
-        anim.end();
+        assertEquals(1, getEndSideContentView().getAlpha(), 0.01);
 
-        // THEN the system info is visible but alpha 0
-        assertEquals(View.VISIBLE, getEndSideContentView().getVisibility());
+        // WHEN the system event animation begins
+        fragment.onSystemEventAnimationBegin().start();
+        mAnimatorTestRule.advanceTimeBy(500);
+
+        // THEN the system info is invisible
+        assertEquals(View.INVISIBLE, getEndSideContentView().getVisibility());
         assertEquals(0, getEndSideContentView().getAlpha(), 0.01);
 
         // WHEN the system event animation finishes
-        when(mAnimationScheduler.getAnimationState()).thenReturn(ANIMATING_OUT);
-        anim = fragment.onSystemEventAnimationFinish(false);
-        anim.start();
-        processAllMessages();
-        anim.end();
+        fragment.onSystemEventAnimationFinish(false).start();
+        mAnimatorTestRule.advanceTimeBy(500);
 
-        // THEN the syste info is full alpha and VISIBLE
+        // THEN the system info is full alpha and VISIBLE
         assertEquals(View.VISIBLE, getEndSideContentView().getVisibility());
         assertEquals(1, getEndSideContentView().getAlpha(), 0.01);
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/MultiSourceMinAlphaControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/MultiSourceMinAlphaControllerTest.kt
new file mode 100644
index 0000000..2617613
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/MultiSourceMinAlphaControllerTest.kt
@@ -0,0 +1,106 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.phone.fragment
+
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper
+import android.view.View
+import androidx.core.animation.AnimatorTestRule
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import junit.framework.Assert.assertEquals
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+private const val TEST_SOURCE_1 = 1
+private const val TEST_SOURCE_2 = 2
+private const val TEST_ANIMATION_DURATION = 100L
+private const val INITIAL_ALPHA = 1f
+
+@RunWith(AndroidTestingRunner::class)
+@TestableLooper.RunWithLooper(setAsMainLooper = true)
+@SmallTest
+class MultiSourceMinAlphaControllerTest : SysuiTestCase() {
+
+    private val view = View(context)
+    private val multiSourceMinAlphaController =
+        MultiSourceMinAlphaController(view, initialAlpha = INITIAL_ALPHA)
+
+    @get:Rule val animatorTestRule = AnimatorTestRule()
+
+    @Before
+    fun setup() {
+        multiSourceMinAlphaController.reset()
+    }
+
+    @Test
+    fun testSetAlpha() {
+        multiSourceMinAlphaController.setAlpha(alpha = 0.5f, sourceId = TEST_SOURCE_1)
+        assertEquals(0.5f, view.alpha)
+    }
+
+    @Test
+    fun testAnimateToAlpha() {
+        multiSourceMinAlphaController.animateToAlpha(
+            alpha = 0.5f,
+            sourceId = TEST_SOURCE_1,
+            duration = TEST_ANIMATION_DURATION
+        )
+        animatorTestRule.advanceTimeBy(TEST_ANIMATION_DURATION)
+        assertEquals(0.5f, view.alpha)
+    }
+
+    @Test
+    fun testReset() {
+        multiSourceMinAlphaController.animateToAlpha(
+            alpha = 0.5f,
+            sourceId = TEST_SOURCE_1,
+            duration = TEST_ANIMATION_DURATION
+        )
+        multiSourceMinAlphaController.setAlpha(alpha = 0.7f, sourceId = TEST_SOURCE_2)
+        multiSourceMinAlphaController.reset()
+        // advance time to ensure that animators are cancelled when the controller is reset
+        animatorTestRule.advanceTimeBy(TEST_ANIMATION_DURATION)
+        assertEquals(INITIAL_ALPHA, view.alpha)
+    }
+
+    @Test
+    fun testMinOfTwoSourcesIsApplied() {
+        multiSourceMinAlphaController.setAlpha(alpha = 0f, sourceId = TEST_SOURCE_1)
+        multiSourceMinAlphaController.setAlpha(alpha = 0.5f, sourceId = TEST_SOURCE_2)
+        assertEquals(0f, view.alpha)
+        multiSourceMinAlphaController.setAlpha(alpha = 1f, sourceId = TEST_SOURCE_1)
+        assertEquals(0.5f, view.alpha)
+    }
+
+    @Test
+    fun testSetAlphaForSameSourceCancelsAnimator() {
+        multiSourceMinAlphaController.animateToAlpha(
+            alpha = 0f,
+            sourceId = TEST_SOURCE_1,
+            duration = TEST_ANIMATION_DURATION
+        )
+        animatorTestRule.advanceTimeBy(TEST_ANIMATION_DURATION / 2)
+        multiSourceMinAlphaController.setAlpha(alpha = 1f, sourceId = TEST_SOURCE_1)
+        animatorTestRule.advanceTimeBy(TEST_ANIMATION_DURATION / 2)
+        // verify that animation was cancelled and the setAlpha call overrides the alpha value of
+        // the animation
+        assertEquals(1f, view.alpha)
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/KeyguardStateControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/KeyguardStateControllerTest.java
index d787ada..5cabcd4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/KeyguardStateControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/KeyguardStateControllerTest.java
@@ -24,6 +24,7 @@
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
+import android.hardware.biometrics.BiometricSourceType;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
 
@@ -43,6 +44,7 @@
 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;
 
@@ -66,6 +68,9 @@
     @Mock
     private KeyguardUpdateMonitorLogger mLogger;
 
+    @Captor
+    private ArgumentCaptor<KeyguardUpdateMonitorCallback> mUpdateCallbackCaptor;
+
     @Before
     public void setup() {
         MockitoAnnotations.initMocks(this);
@@ -84,6 +89,23 @@
     }
 
     @Test
+    public void testFaceAuthEnabledChanged_calledWhenFaceEnrollmentStateChanges() {
+        KeyguardStateController.Callback callback = mock(KeyguardStateController.Callback.class);
+
+        when(mKeyguardUpdateMonitor.isFaceAuthEnabledForUser(anyInt())).thenReturn(false);
+        verify(mKeyguardUpdateMonitor).registerCallback(mUpdateCallbackCaptor.capture());
+        mKeyguardStateController.addCallback(callback);
+        assertThat(mKeyguardStateController.isFaceAuthEnabled()).isFalse();
+
+        when(mKeyguardUpdateMonitor.isFaceAuthEnabledForUser(anyInt())).thenReturn(true);
+        mUpdateCallbackCaptor.getValue().onBiometricEnrollmentStateChanged(
+                BiometricSourceType.FACE);
+
+        assertThat(mKeyguardStateController.isFaceAuthEnabled()).isTrue();
+        verify(callback).onFaceAuthEnabledChanged();
+    }
+
+    @Test
     public void testIsShowing() {
         assertThat(mKeyguardStateController.isShowing()).isFalse();
         mKeyguardStateController.notifyKeyguardState(true /* showing */, false /* occluded */);
@@ -177,16 +199,14 @@
     @Test
     public void testOnEnabledTrustAgentsChangedCallback() {
         final Random random = new Random();
-        final ArgumentCaptor<KeyguardUpdateMonitorCallback> updateCallbackCaptor =
-                ArgumentCaptor.forClass(KeyguardUpdateMonitorCallback.class);
 
-        verify(mKeyguardUpdateMonitor).registerCallback(updateCallbackCaptor.capture());
+        verify(mKeyguardUpdateMonitor).registerCallback(mUpdateCallbackCaptor.capture());
         final KeyguardStateController.Callback stateCallback =
                 mock(KeyguardStateController.Callback.class);
         mKeyguardStateController.addCallback(stateCallback);
 
         when(mLockPatternUtils.isSecure(anyInt())).thenReturn(true);
-        updateCallbackCaptor.getValue().onEnabledTrustAgentsChanged(random.nextInt());
+        mUpdateCallbackCaptor.getValue().onEnabledTrustAgentsChanged(random.nextInt());
         verify(stateCallback).onUnlockedChanged();
     }
 }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/authentication/data/repository/FakeAuthenticationRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/authentication/data/repository/FakeAuthenticationRepository.kt
new file mode 100644
index 0000000..a718f70
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/authentication/data/repository/FakeAuthenticationRepository.kt
@@ -0,0 +1,62 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.authentication.data.repository
+
+import com.android.keyguard.KeyguardSecurityModel.SecurityMode
+import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.asStateFlow
+
+class FakeAuthenticationRepository(
+    private val delegate: AuthenticationRepository,
+    private val onSecurityModeChanged: (SecurityMode) -> Unit,
+) : AuthenticationRepository by delegate {
+
+    private val _isUnlocked = MutableStateFlow(false)
+    override val isUnlocked: StateFlow<Boolean> = _isUnlocked.asStateFlow()
+
+    private var authenticationMethod: AuthenticationMethodModel = DEFAULT_AUTHENTICATION_METHOD
+
+    override suspend fun getAuthenticationMethod(): AuthenticationMethodModel {
+        return authenticationMethod
+    }
+
+    fun setAuthenticationMethod(authenticationMethod: AuthenticationMethodModel) {
+        this.authenticationMethod = authenticationMethod
+        onSecurityModeChanged(authenticationMethod.toSecurityMode())
+    }
+
+    fun setUnlocked(isUnlocked: Boolean) {
+        _isUnlocked.value = isUnlocked
+    }
+
+    companion object {
+        val DEFAULT_AUTHENTICATION_METHOD =
+            AuthenticationMethodModel.Pin(listOf(1, 2, 3, 4), autoConfirm = false)
+
+        fun AuthenticationMethodModel.toSecurityMode(): SecurityMode {
+            return when (this) {
+                is AuthenticationMethodModel.Pin -> SecurityMode.PIN
+                is AuthenticationMethodModel.Password -> SecurityMode.Password
+                is AuthenticationMethodModel.Pattern -> SecurityMode.Pattern
+                is AuthenticationMethodModel.Swipe,
+                is AuthenticationMethodModel.None -> SecurityMode.None
+            }
+        }
+    }
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeDeviceEntryFaceAuthRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeDeviceEntryFaceAuthRepository.kt
index 738f09d..2715aaa 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeDeviceEntryFaceAuthRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeDeviceEntryFaceAuthRepository.kt
@@ -41,7 +41,9 @@
     fun setDetectionStatus(status: DetectionStatus) {
         _detectionStatus.value = status
     }
-    override val isLockedOut = MutableStateFlow(false)
+
+    private val _isLockedOut = MutableStateFlow(false)
+    override val isLockedOut = _isLockedOut
     private val _runningAuthRequest = MutableStateFlow<Pair<FaceAuthUiEvent, Boolean>?>(null)
     val runningAuthRequest: StateFlow<Pair<FaceAuthUiEvent, Boolean>?> =
         _runningAuthRequest.asStateFlow()
@@ -56,6 +58,10 @@
         _isAuthRunning.value = true
     }
 
+    fun setLockedOut(value: Boolean) {
+        _isLockedOut.value = value
+    }
+
     override fun cancel() {
         _isAuthRunning.value = false
         _runningAuthRequest.value = null
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/data/repository/FakeAutoAddRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/data/repository/FakeAutoAddRepository.kt
new file mode 100644
index 0000000..9ea079f
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/data/repository/FakeAutoAddRepository.kt
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.pipeline.data.repository
+
+import com.android.systemui.qs.pipeline.shared.TileSpec
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
+
+class FakeAutoAddRepository : AutoAddRepository {
+
+    private val autoAddedTilesPerUser = mutableMapOf<Int, MutableStateFlow<Set<TileSpec>>>()
+
+    override fun autoAddedTiles(userId: Int): Flow<Set<TileSpec>> {
+        return getFlow(userId)
+    }
+
+    override suspend fun markTileAdded(userId: Int, spec: TileSpec) {
+        if (spec == TileSpec.Invalid) return
+        with(getFlow(userId)) { value = value.toMutableSet().apply { add(spec) } }
+    }
+
+    override suspend fun unmarkTileAdded(userId: Int, spec: TileSpec) {
+        with(getFlow(userId)) { value = value.toMutableSet().apply { remove(spec) } }
+    }
+
+    private fun getFlow(userId: Int): MutableStateFlow<Set<TileSpec>> =
+        autoAddedTilesPerUser.getOrPut(userId) { MutableStateFlow(emptySet()) }
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/domain/autoaddable/FakeAutoAddable.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/domain/autoaddable/FakeAutoAddable.kt
new file mode 100644
index 0000000..ebdd6fd
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/domain/autoaddable/FakeAutoAddable.kt
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.pipeline.domain.autoaddable
+
+import com.android.systemui.qs.pipeline.data.repository.TileSpecRepository.Companion.POSITION_AT_END
+import com.android.systemui.qs.pipeline.domain.model.AutoAddSignal
+import com.android.systemui.qs.pipeline.domain.model.AutoAddTracking
+import com.android.systemui.qs.pipeline.domain.model.AutoAddable
+import com.android.systemui.qs.pipeline.shared.TileSpec
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.flow.filterNotNull
+
+class FakeAutoAddable(
+    private val spec: TileSpec,
+    override val autoAddTracking: AutoAddTracking,
+) : AutoAddable {
+
+    private val signalsPerUser = mutableMapOf<Int, MutableStateFlow<AutoAddSignal?>>()
+    private fun getFlow(userId: Int): MutableStateFlow<AutoAddSignal?> =
+        signalsPerUser.getOrPut(userId) { MutableStateFlow(null) }
+
+    override fun autoAddSignal(userId: Int): Flow<AutoAddSignal> {
+        return getFlow(userId).asStateFlow().filterNotNull()
+    }
+
+    suspend fun sendRemoveSignal(userId: Int) {
+        getFlow(userId).value = AutoAddSignal.Remove(spec)
+    }
+
+    suspend fun sendAddSignal(userId: Int, position: Int = POSITION_AT_END) {
+        getFlow(userId).value = AutoAddSignal.Add(spec, position)
+    }
+
+    override val description: String
+        get() = "FakeAutoAddable($spec)"
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneTestUtils.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneTestUtils.kt
index 9c4fd94..0b6e2a2 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneTestUtils.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneTestUtils.kt
@@ -16,20 +16,28 @@
 
 package com.android.systemui.scene
 
+import com.android.keyguard.KeyguardSecurityModel.SecurityMode
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.authentication.data.repository.AuthenticationRepository
 import com.android.systemui.authentication.data.repository.AuthenticationRepositoryImpl
+import com.android.systemui.authentication.data.repository.FakeAuthenticationRepository
+import com.android.systemui.authentication.data.repository.FakeAuthenticationRepository.Companion.toSecurityMode
 import com.android.systemui.authentication.domain.interactor.AuthenticationInteractor
 import com.android.systemui.bouncer.data.repository.BouncerRepository
 import com.android.systemui.bouncer.domain.interactor.BouncerInteractor
 import com.android.systemui.bouncer.ui.viewmodel.BouncerViewModel
+import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
 import com.android.systemui.keyguard.domain.interactor.LockscreenSceneInteractor
 import com.android.systemui.scene.data.repository.SceneContainerRepository
 import com.android.systemui.scene.domain.interactor.SceneInteractor
 import com.android.systemui.scene.shared.model.SceneContainerConfig
 import com.android.systemui.scene.shared.model.SceneKey
+import com.android.systemui.user.data.repository.FakeUserRepository
+import com.android.systemui.util.mockito.mock
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.StandardTestDispatcher
+import kotlinx.coroutines.test.TestDispatcher
 import kotlinx.coroutines.test.TestScope
 
 /**
@@ -39,9 +47,25 @@
 @OptIn(ExperimentalCoroutinesApi::class)
 class SceneTestUtils(
     test: SysuiTestCase,
-    private val testScope: TestScope? = null,
 ) {
-
+    val testDispatcher: TestDispatcher by lazy { StandardTestDispatcher() }
+    val testScope: TestScope by lazy { TestScope(testDispatcher) }
+    private var securityMode: SecurityMode =
+        FakeAuthenticationRepository.DEFAULT_AUTHENTICATION_METHOD.toSecurityMode()
+    val authenticationRepository: FakeAuthenticationRepository by lazy {
+        FakeAuthenticationRepository(
+            delegate =
+                AuthenticationRepositoryImpl(
+                    applicationScope = applicationScope(),
+                    getSecurityMode = { securityMode },
+                    backgroundDispatcher = testDispatcher,
+                    userRepository = FakeUserRepository(),
+                    lockPatternUtils = mock(),
+                    keyguardRepository = FakeKeyguardRepository(),
+                ),
+            onSecurityModeChanged = { securityMode = it },
+        )
+    }
     private val context = test.context
 
     fun fakeSceneContainerRepository(
@@ -82,7 +106,7 @@
     }
 
     fun authenticationRepository(): AuthenticationRepository {
-        return AuthenticationRepositoryImpl()
+        return authenticationRepository
     }
 
     fun authenticationInteractor(
@@ -94,17 +118,6 @@
         )
     }
 
-    private fun applicationScope(): CoroutineScope {
-        return checkNotNull(testScope) {
-                """
-                TestScope not initialized, please create a TestScope and inject it into
-                SceneTestUtils.
-            """
-                    .trimIndent()
-            }
-            .backgroundScope
-    }
-
     fun bouncerInteractor(
         authenticationInteractor: AuthenticationInteractor,
         sceneInteractor: SceneInteractor,
@@ -154,6 +167,10 @@
         )
     }
 
+    private fun applicationScope(): CoroutineScope {
+        return testScope.backgroundScope
+    }
+
     companion object {
         const val CONTAINER_1 = "container1"
         const val CONTAINER_2 = "container2"
diff --git a/services/core/java/com/android/server/am/ActivityManagerConstants.java b/services/core/java/com/android/server/am/ActivityManagerConstants.java
index ae24f1e..de6522e 100644
--- a/services/core/java/com/android/server/am/ActivityManagerConstants.java
+++ b/services/core/java/com/android/server/am/ActivityManagerConstants.java
@@ -1027,7 +1027,7 @@
     private static final String KEY_ENABLE_WAIT_FOR_FINISH_ATTACH_APPLICATION =
             "enable_wait_for_finish_attach_application";
 
-    private static final boolean DEFAULT_ENABLE_WAIT_FOR_FINISH_ATTACH_APPLICATION = false;
+    private static final boolean DEFAULT_ENABLE_WAIT_FOR_FINISH_ATTACH_APPLICATION = true;
 
     /** @see #KEY_ENABLE_WAIT_FOR_FINISH_ATTACH_APPLICATION */
     public volatile boolean mEnableWaitForFinishAttachApplication =
diff --git a/services/core/java/com/android/server/am/ProcessErrorStateRecord.java b/services/core/java/com/android/server/am/ProcessErrorStateRecord.java
index e498384..753fdae 100644
--- a/services/core/java/com/android/server/am/ProcessErrorStateRecord.java
+++ b/services/core/java/com/android/server/am/ProcessErrorStateRecord.java
@@ -51,11 +51,11 @@
 import com.android.internal.annotations.CompositeRWLock;
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
-import com.android.modules.expresslog.Counter;
 import com.android.internal.os.ProcessCpuTracker;
 import com.android.internal.os.TimeoutRecord;
 import com.android.internal.os.anr.AnrLatencyTracker;
 import com.android.internal.util.FrameworkStatsLog;
+import com.android.modules.expresslog.Counter;
 import com.android.server.ResourcePressureUtil;
 import com.android.server.criticalevents.CriticalEventLog;
 import com.android.server.stats.pull.ProcfsMemoryUtil.MemorySnapshot;
@@ -456,6 +456,11 @@
         String currentPsiState = ResourcePressureUtil.currentPsiState();
         latencyTracker.currentPsiStateReturned();
         report.append(currentPsiState);
+        // The 'processCpuTracker' variable is a shared resource that might be initialized and
+        // updated in a different thread. In order to prevent thread visibility issues, which
+        // can occur when one thread does not immediately see the changes made to
+        // 'processCpuTracker' by another thread, it is necessary to use synchronization whenever
+        // 'processCpuTracker' is accessed or modified.
         ProcessCpuTracker processCpuTracker = new ProcessCpuTracker(true);
 
         // We push the native pids collection task to the helper thread through
@@ -517,12 +522,16 @@
             }
             mService.updateCpuStatsNow();
             mService.mAppProfiler.printCurrentCpuState(report, anrTime);
-            info.append(processCpuTracker.printCurrentLoad());
+            synchronized (processCpuTracker) {
+                info.append(processCpuTracker.printCurrentLoad());
+            }
             info.append(report);
         }
         report.append(tracesFileException.getBuffer());
 
-        info.append(processCpuTracker.printCurrentState(anrTime));
+        synchronized (processCpuTracker) {
+            info.append(processCpuTracker.printCurrentState(anrTime));
+        }
 
         Slog.e(TAG, info.toString());
         if (tracesFile == null) {
diff --git a/services/core/java/com/android/server/am/StackTracesDumpHelper.java b/services/core/java/com/android/server/am/StackTracesDumpHelper.java
index cf69b53..ba0fd17 100644
--- a/services/core/java/com/android/server/am/StackTracesDumpHelper.java
+++ b/services/core/java/com/android/server/am/StackTracesDumpHelper.java
@@ -464,28 +464,31 @@
             latencyTracker.processCpuTrackerMethodsCalled();
         }
         ArrayList<Integer> extraPids = new ArrayList<>();
-        processCpuTracker.init();
+        synchronized (processCpuTracker) {
+            processCpuTracker.init();
+        }
         try {
             Thread.sleep(200);
         } catch (InterruptedException ignored) {
         }
 
-        processCpuTracker.update();
+        synchronized (processCpuTracker) {
+            processCpuTracker.update();
+            // We'll take the stack crawls of just the top apps using CPU.
+            final int workingStatsNumber = processCpuTracker.countWorkingStats();
+            for (int i = 0; i < workingStatsNumber && extraPids.size() < 2; i++) {
+                ProcessCpuTracker.Stats stats = processCpuTracker.getWorkingStats(i);
+                if (lastPids.indexOfKey(stats.pid) >= 0) {
+                    if (DEBUG_ANR) {
+                        Slog.d(TAG, "Collecting stacks for extra pid " + stats.pid);
+                    }
 
-        // We'll take the stack crawls of just the top apps using CPU.
-        final int workingStatsNumber = processCpuTracker.countWorkingStats();
-        for (int i = 0; i < workingStatsNumber && extraPids.size() < 2; i++) {
-            ProcessCpuTracker.Stats stats = processCpuTracker.getWorkingStats(i);
-            if (lastPids.indexOfKey(stats.pid) >= 0) {
-                if (DEBUG_ANR) {
-                    Slog.d(TAG, "Collecting stacks for extra pid " + stats.pid);
+                    extraPids.add(stats.pid);
+                } else {
+                    Slog.i(TAG,
+                            "Skipping next CPU consuming process, not a java proc: "
+                            + stats.pid);
                 }
-
-                extraPids.add(stats.pid);
-            } else {
-                Slog.i(TAG,
-                        "Skipping next CPU consuming process, not a java proc: "
-                        + stats.pid);
             }
         }
         if (latencyTracker != null) {
diff --git a/services/core/java/com/android/server/app/GameManagerService.java b/services/core/java/com/android/server/app/GameManagerService.java
index e4a5a3e..ca15dd7 100644
--- a/services/core/java/com/android/server/app/GameManagerService.java
+++ b/services/core/java/com/android/server/app/GameManagerService.java
@@ -2166,7 +2166,7 @@
         @Override
         public void onUidStateChanged(int uid, int procState, long procStateSeq, int capability) {
             synchronized (mUidObserverLock) {
-                if (ActivityManager.isProcStateBackground(procState)) {
+                if (procState != ActivityManager.PROCESS_STATE_TOP) {
                     disableGameMode(uid);
                     return;
                 }
diff --git a/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java b/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java
index 50ffbcb9..3f4f981 100644
--- a/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java
+++ b/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java
@@ -957,6 +957,7 @@
             players = (HashMap<Integer, AudioPlaybackConfiguration>) mPlayers.clone();
         }
         mFadingManager.unfadeOutUid(uid, players);
+        mDuckingManager.unduckUid(uid, players);
     }
 
     //=================================================================
diff --git a/services/core/java/com/android/server/biometrics/sensors/SensorOverlays.java b/services/core/java/com/android/server/biometrics/sensors/SensorOverlays.java
index 969a174..aeb6b6e 100644
--- a/services/core/java/com/android/server/biometrics/sensors/SensorOverlays.java
+++ b/services/core/java/com/android/server/biometrics/sensors/SensorOverlays.java
@@ -20,7 +20,6 @@
 import android.annotation.Nullable;
 import android.hardware.biometrics.BiometricOverlayConstants;
 import android.hardware.fingerprint.ISidefpsController;
-import android.hardware.fingerprint.IUdfpsOverlay;
 import android.hardware.fingerprint.IUdfpsOverlayController;
 import android.hardware.fingerprint.IUdfpsOverlayControllerCallback;
 import android.os.RemoteException;
@@ -44,7 +43,6 @@
 
     @NonNull private final Optional<IUdfpsOverlayController> mUdfpsOverlayController;
     @NonNull private final Optional<ISidefpsController> mSidefpsController;
-    @NonNull private final Optional<IUdfpsOverlay> mUdfpsOverlay;
 
     /**
      * Create an overlay controller for each modality.
@@ -54,11 +52,9 @@
      */
     public SensorOverlays(
             @Nullable IUdfpsOverlayController udfpsOverlayController,
-            @Nullable ISidefpsController sidefpsController,
-            @Nullable IUdfpsOverlay udfpsOverlay) {
+            @Nullable ISidefpsController sidefpsController) {
         mUdfpsOverlayController = Optional.ofNullable(udfpsOverlayController);
         mSidefpsController = Optional.ofNullable(sidefpsController);
-        mUdfpsOverlay = Optional.ofNullable(udfpsOverlay);
     }
 
     /**
@@ -94,14 +90,6 @@
                 Slog.e(TAG, "Remote exception when showing the UDFPS overlay", e);
             }
         }
-
-        if (mUdfpsOverlay.isPresent()) {
-            try {
-                mUdfpsOverlay.get().show(client.getRequestId(), sensorId, reason);
-            } catch (RemoteException e) {
-                Slog.e(TAG, "Remote exception when showing the new UDFPS overlay", e);
-            }
-        }
     }
 
     /**
@@ -125,14 +113,6 @@
                 Slog.e(TAG, "Remote exception when hiding the UDFPS overlay", e);
             }
         }
-
-        if (mUdfpsOverlay.isPresent()) {
-            try {
-                mUdfpsOverlay.get().hide(sensorId);
-            } catch (RemoteException e) {
-                Slog.e(TAG, "Remote exception when hiding the new udfps overlay", e);
-            }
-        }
     }
 
     /**
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java
index ea6bb62..28cb7d9 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java
@@ -54,7 +54,6 @@
 import android.hardware.fingerprint.IFingerprintService;
 import android.hardware.fingerprint.IFingerprintServiceReceiver;
 import android.hardware.fingerprint.ISidefpsController;
-import android.hardware.fingerprint.IUdfpsOverlay;
 import android.hardware.fingerprint.IUdfpsOverlayController;
 import android.os.Binder;
 import android.os.Build;
@@ -963,16 +962,6 @@
 
         @android.annotation.EnforcePermission(android.Manifest.permission.USE_BIOMETRIC_INTERNAL)
         @Override
-        public void setUdfpsOverlay(@NonNull IUdfpsOverlay controller) {
-            super.setUdfpsOverlay_enforcePermission();
-
-            for (ServiceProvider provider : mRegistry.getProviders()) {
-                provider.setUdfpsOverlay(controller);
-            }
-        }
-
-        @android.annotation.EnforcePermission(android.Manifest.permission.USE_BIOMETRIC_INTERNAL)
-        @Override
         public void onPowerPressed() {
             super.onPowerPressed_enforcePermission();
 
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/ServiceProvider.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/ServiceProvider.java
index d70ca8c..a15d1a4 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/ServiceProvider.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/ServiceProvider.java
@@ -28,7 +28,6 @@
 import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
 import android.hardware.fingerprint.IFingerprintServiceReceiver;
 import android.hardware.fingerprint.ISidefpsController;
-import android.hardware.fingerprint.IUdfpsOverlay;
 import android.hardware.fingerprint.IUdfpsOverlayController;
 import android.os.IBinder;
 
@@ -134,12 +133,6 @@
 
     void setUdfpsOverlayController(@NonNull IUdfpsOverlayController controller);
 
-    /**
-     * Sets udfps overlay
-     * @param controller udfps overlay
-     */
-    void setUdfpsOverlay(@NonNull IUdfpsOverlay controller);
-
     void onPowerPressed();
 
     /**
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java
index 3fc36b6..54d1faa 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java
@@ -30,7 +30,6 @@
 import android.hardware.fingerprint.FingerprintManager;
 import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
 import android.hardware.fingerprint.ISidefpsController;
-import android.hardware.fingerprint.IUdfpsOverlay;
 import android.hardware.fingerprint.IUdfpsOverlayController;
 import android.os.Build;
 import android.os.Handler;
@@ -112,7 +111,6 @@
             @NonNull LockoutCache lockoutCache,
             @Nullable IUdfpsOverlayController udfpsOverlayController,
             @Nullable ISidefpsController sidefpsController,
-            @Nullable IUdfpsOverlay udfpsOverlay,
             boolean allowBackgroundAuthentication,
             @NonNull FingerprintSensorPropertiesInternal sensorProps,
             @NonNull Handler handler,
@@ -137,8 +135,7 @@
                 false /* shouldVibrate */,
                 biometricStrength);
         setRequestId(requestId);
-        mSensorOverlays = new SensorOverlays(udfpsOverlayController,
-                sidefpsController, udfpsOverlay);
+        mSensorOverlays = new SensorOverlays(udfpsOverlayController, sidefpsController);
         mSensorProps = sensorProps;
         mALSProbeCallback = getLogger().getAmbientLightProbe(false /* startWithClient */);
         mHandler = handler;
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintDetectClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintDetectClient.java
index 46f62d3..51a9385 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintDetectClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintDetectClient.java
@@ -22,7 +22,6 @@
 import android.hardware.biometrics.BiometricOverlayConstants;
 import android.hardware.biometrics.common.ICancellationSignal;
 import android.hardware.fingerprint.FingerprintAuthenticateOptions;
-import android.hardware.fingerprint.IUdfpsOverlay;
 import android.hardware.fingerprint.IUdfpsOverlayController;
 import android.os.IBinder;
 import android.os.RemoteException;
@@ -59,15 +58,13 @@
             @NonNull FingerprintAuthenticateOptions options,
             @NonNull BiometricLogger biometricLogger, @NonNull BiometricContext biometricContext,
             @Nullable IUdfpsOverlayController udfpsOverlayController,
-            @Nullable IUdfpsOverlay udfpsOverlay,
             boolean isStrongBiometric) {
         super(context, lazyDaemon, token, listener, options.getUserId(),
                 options.getOpPackageName(), 0 /* cookie */, options.getSensorId(),
                 true /* shouldVibrate */, biometricLogger, biometricContext);
         setRequestId(requestId);
         mIsStrongBiometric = isStrongBiometric;
-        mSensorOverlays = new SensorOverlays(udfpsOverlayController,
-                null /* sideFpsController*/, udfpsOverlay);
+        mSensorOverlays = new SensorOverlays(udfpsOverlayController, null /* sideFpsController*/);
         mOptions = options;
     }
 
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClient.java
index d35469c..f9e08d6 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClient.java
@@ -29,7 +29,6 @@
 import android.hardware.fingerprint.FingerprintManager;
 import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
 import android.hardware.fingerprint.ISidefpsController;
-import android.hardware.fingerprint.IUdfpsOverlay;
 import android.hardware.fingerprint.IUdfpsOverlayController;
 import android.hardware.keymaster.HardwareAuthToken;
 import android.os.IBinder;
@@ -87,7 +86,6 @@
             @NonNull FingerprintSensorPropertiesInternal sensorProps,
             @Nullable IUdfpsOverlayController udfpsOverlayController,
             @Nullable ISidefpsController sidefpsController,
-            @Nullable IUdfpsOverlay udfpsOverlay,
             int maxTemplatesPerUser, @FingerprintManager.EnrollReason int enrollReason) {
         // UDFPS haptics occur when an image is acquired (instead of when the result is known)
         super(context, lazyDaemon, token, listener, userId, hardwareAuthToken, owner, utils,
@@ -95,8 +93,7 @@
                 biometricContext);
         setRequestId(requestId);
         mSensorProps = sensorProps;
-        mSensorOverlays = new SensorOverlays(udfpsOverlayController,
-                sidefpsController, udfpsOverlay);
+        mSensorOverlays = new SensorOverlays(udfpsOverlayController, sidefpsController);
         mMaxTemplatesPerUser = maxTemplatesPerUser;
 
         mALSProbeCallback = getLogger().getAmbientLightProbe(true /* startWithClient */);
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java
index f8d2566..0421d78 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java
@@ -43,7 +43,6 @@
 import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
 import android.hardware.fingerprint.IFingerprintServiceReceiver;
 import android.hardware.fingerprint.ISidefpsController;
-import android.hardware.fingerprint.IUdfpsOverlay;
 import android.hardware.fingerprint.IUdfpsOverlayController;
 import android.os.Binder;
 import android.os.Handler;
@@ -122,7 +121,6 @@
     @Nullable private IFingerprint mDaemon;
     @Nullable private IUdfpsOverlayController mUdfpsOverlayController;
     @Nullable private ISidefpsController mSidefpsController;
-    @Nullable private IUdfpsOverlay mUdfpsOverlay;
     private AuthSessionCoordinator mAuthSessionCoordinator;
 
     private final class BiometricTaskStackListener extends TaskStackListener {
@@ -420,7 +418,7 @@
                             BiometricsProtoEnums.CLIENT_UNKNOWN),
                     mBiometricContext,
                     mFingerprintSensors.get(sensorId).getSensorProperties(),
-                    mUdfpsOverlayController, mSidefpsController, mUdfpsOverlay,
+                    mUdfpsOverlayController, mSidefpsController,
                     maxTemplatesPerUser, enrollReason);
             scheduleForSensor(sensorId, client, new ClientMonitorCompositeCallback(
                     mBiometricStateCallback, new ClientMonitorCallback() {
@@ -458,8 +456,7 @@
                     mFingerprintSensors.get(sensorId).getLazySession(), token, id, callback,
                     options,
                     createLogger(BiometricsProtoEnums.ACTION_AUTHENTICATE, statsClient),
-                    mBiometricContext,
-                    mUdfpsOverlayController, mUdfpsOverlay, isStrongBiometric);
+                    mBiometricContext, mUdfpsOverlayController, isStrongBiometric);
             scheduleForSensor(sensorId, client, mBiometricStateCallback);
         });
 
@@ -483,7 +480,7 @@
                     createLogger(BiometricsProtoEnums.ACTION_AUTHENTICATE, statsClient),
                     mBiometricContext, isStrongBiometric,
                     mTaskStackListener, mFingerprintSensors.get(sensorId).getLockoutCache(),
-                    mUdfpsOverlayController, mSidefpsController, mUdfpsOverlay,
+                    mUdfpsOverlayController, mSidefpsController,
                     allowBackgroundAuthentication,
                     mFingerprintSensors.get(sensorId).getSensorProperties(), mHandler,
                     Utils.getCurrentStrength(sensorId),
@@ -719,11 +716,6 @@
     }
 
     @Override
-    public void setUdfpsOverlay(@NonNull IUdfpsOverlay controller) {
-        mUdfpsOverlay = controller;
-    }
-
-    @Override
     public void dumpProtoState(int sensorId, @NonNull ProtoOutputStream proto,
             boolean clearSchedulerBuffer) {
         if (mFingerprintSensors.contains(sensorId)) {
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21.java
index 1cbbf89..92b216d 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21.java
@@ -40,7 +40,6 @@
 import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
 import android.hardware.fingerprint.IFingerprintServiceReceiver;
 import android.hardware.fingerprint.ISidefpsController;
-import android.hardware.fingerprint.IUdfpsOverlay;
 import android.hardware.fingerprint.IUdfpsOverlayController;
 import android.os.Handler;
 import android.os.IBinder;
@@ -123,7 +122,6 @@
     @NonNull private final HalResultController mHalResultController;
     @Nullable private IUdfpsOverlayController mUdfpsOverlayController;
     @Nullable private ISidefpsController mSidefpsController;
-    @Nullable private IUdfpsOverlay mUdfpsOverlay;
     @NonNull private final BiometricContext mBiometricContext;
     // for requests that do not use biometric prompt
     @NonNull private final AtomicLong mRequestCounter = new AtomicLong(0);
@@ -597,9 +595,7 @@
                     mSensorProperties.sensorId,
                     createLogger(BiometricsProtoEnums.ACTION_ENROLL,
                             BiometricsProtoEnums.CLIENT_UNKNOWN),
-                    mBiometricContext,
-                    mUdfpsOverlayController, mSidefpsController, mUdfpsOverlay,
-                    enrollReason);
+                    mBiometricContext, mUdfpsOverlayController, mSidefpsController, enrollReason);
             mScheduler.scheduleClientMonitor(client, new ClientMonitorCallback() {
                 @Override
                 public void onClientStarted(@NonNull BaseClientMonitor clientMonitor) {
@@ -644,8 +640,7 @@
             final FingerprintDetectClient client = new FingerprintDetectClient(mContext,
                     mLazyDaemon, token, id, listener, options,
                     createLogger(BiometricsProtoEnums.ACTION_AUTHENTICATE, statsClient),
-                    mBiometricContext, mUdfpsOverlayController, mUdfpsOverlay,
-                    isStrongBiometric);
+                    mBiometricContext, mUdfpsOverlayController, isStrongBiometric);
             mScheduler.scheduleClientMonitor(client, mBiometricStateCallback);
         });
 
@@ -668,7 +663,7 @@
                     createLogger(BiometricsProtoEnums.ACTION_AUTHENTICATE, statsClient),
                     mBiometricContext, isStrongBiometric,
                     mTaskStackListener, mLockoutTracker,
-                    mUdfpsOverlayController, mSidefpsController, mUdfpsOverlay,
+                    mUdfpsOverlayController, mSidefpsController,
                     allowBackgroundAuthentication, mSensorProperties,
                     Utils.getCurrentStrength(mSensorId));
             mScheduler.scheduleClientMonitor(client, mBiometricStateCallback);
@@ -856,11 +851,6 @@
     }
 
     @Override
-    public void setUdfpsOverlay(@NonNull IUdfpsOverlay controller) {
-        mUdfpsOverlay = controller;
-    }
-
-    @Override
     public void dumpProtoState(int sensorId, @NonNull ProtoOutputStream proto,
             boolean clearSchedulerBuffer) {
         final long sensorToken = proto.start(SensorServiceStateProto.SENSOR_STATES);
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintAuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintAuthenticationClient.java
index 2a62338..9966e91 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintAuthenticationClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintAuthenticationClient.java
@@ -30,7 +30,6 @@
 import android.hardware.fingerprint.FingerprintManager;
 import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
 import android.hardware.fingerprint.ISidefpsController;
-import android.hardware.fingerprint.IUdfpsOverlay;
 import android.hardware.fingerprint.IUdfpsOverlayController;
 import android.os.IBinder;
 import android.os.RemoteException;
@@ -83,7 +82,6 @@
             @NonNull LockoutFrameworkImpl lockoutTracker,
             @Nullable IUdfpsOverlayController udfpsOverlayController,
             @Nullable ISidefpsController sidefpsController,
-            @Nullable IUdfpsOverlay udfpsOverlay,
             boolean allowBackgroundAuthentication,
             @NonNull FingerprintSensorPropertiesInternal sensorProps,
             @Authenticators.Types int sensorStrength) {
@@ -93,8 +91,7 @@
                 false /* shouldVibrate */, sensorStrength);
         setRequestId(requestId);
         mLockoutFrameworkImpl = lockoutTracker;
-        mSensorOverlays = new SensorOverlays(udfpsOverlayController,
-                sidefpsController, udfpsOverlay);
+        mSensorOverlays = new SensorOverlays(udfpsOverlayController, sidefpsController);
         mSensorProps = sensorProps;
         mALSProbeCallback = getLogger().getAmbientLightProbe(false /* startWithClient */);
     }
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintDetectClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintDetectClient.java
index ed0a201..0d7f9f2 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintDetectClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintDetectClient.java
@@ -26,7 +26,6 @@
 import android.hardware.biometrics.fingerprint.V2_1.IBiometricsFingerprint;
 import android.hardware.fingerprint.FingerprintAuthenticateOptions;
 import android.hardware.fingerprint.FingerprintManager;
-import android.hardware.fingerprint.IUdfpsOverlay;
 import android.hardware.fingerprint.IUdfpsOverlayController;
 import android.os.IBinder;
 import android.os.RemoteException;
@@ -67,13 +66,12 @@
             @NonNull FingerprintAuthenticateOptions options,
             @NonNull BiometricLogger biometricLogger, @NonNull BiometricContext biometricContext,
             @Nullable IUdfpsOverlayController udfpsOverlayController,
-            @Nullable IUdfpsOverlay udfpsOverlay, boolean isStrongBiometric) {
+            boolean isStrongBiometric) {
         super(context, lazyDaemon, token, listener, options.getUserId(),
                 options.getOpPackageName(), 0 /* cookie */, options.getSensorId(),
                 true /* shouldVibrate */, biometricLogger, biometricContext);
         setRequestId(requestId);
-        mSensorOverlays = new SensorOverlays(udfpsOverlayController,
-                null /* sideFpsController */, udfpsOverlay);
+        mSensorOverlays = new SensorOverlays(udfpsOverlayController, null /* sideFpsController */);
         mIsStrongBiometric = isStrongBiometric;
     }
 
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintEnrollClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintEnrollClient.java
index c2b7944..6fee84a 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintEnrollClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintEnrollClient.java
@@ -27,7 +27,6 @@
 import android.hardware.fingerprint.Fingerprint;
 import android.hardware.fingerprint.FingerprintManager;
 import android.hardware.fingerprint.ISidefpsController;
-import android.hardware.fingerprint.IUdfpsOverlay;
 import android.hardware.fingerprint.IUdfpsOverlayController;
 import android.os.IBinder;
 import android.os.RemoteException;
@@ -69,14 +68,12 @@
             @NonNull BiometricLogger biometricLogger, @NonNull BiometricContext biometricContext,
             @Nullable IUdfpsOverlayController udfpsOverlayController,
             @Nullable ISidefpsController sidefpsController,
-            @Nullable IUdfpsOverlay udfpsOverlay,
             @FingerprintManager.EnrollReason int enrollReason) {
         super(context, lazyDaemon, token, listener, userId, hardwareAuthToken, owner, utils,
                 timeoutSec, sensorId, true /* shouldVibrate */, biometricLogger,
                 biometricContext);
         setRequestId(requestId);
-        mSensorOverlays = new SensorOverlays(udfpsOverlayController,
-                sidefpsController, udfpsOverlay);
+        mSensorOverlays = new SensorOverlays(udfpsOverlayController, sidefpsController);
 
         mEnrollReason = enrollReason;
         if (enrollReason == FingerprintManager.ENROLL_FIND_SENSOR) {
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodBindingController.java b/services/core/java/com/android/server/inputmethod/InputMethodBindingController.java
index b0b1d67..ba9e280 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodBindingController.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodBindingController.java
@@ -352,7 +352,6 @@
                     clearCurMethodAndSessions();
                     mService.clearInputShownLocked();
                     mService.unbindCurrentClientLocked(UnbindReason.DISCONNECT_IME);
-                    mService.resetSystemUiLocked();
                 }
             }
         }
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index 4dbd820..1ab83f7 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -48,6 +48,7 @@
 
 import static com.android.server.inputmethod.ImeVisibilityStateComputer.ImeTargetWindowState;
 import static com.android.server.inputmethod.ImeVisibilityStateComputer.ImeVisibilityResult;
+import static com.android.server.inputmethod.ImeVisibilityStateComputer.STATE_HIDE_IME;
 import static com.android.server.inputmethod.InputMethodBindingController.TIME_TO_RECONNECT;
 import static com.android.server.inputmethod.InputMethodUtils.isSoftInputModeStateVisibleAllowed;
 
@@ -633,9 +634,8 @@
     private InputMethodSubtype mCurrentSubtype;
 
     /**
-     * {@code true} if the IME has not been mostly hidden via {@link android.view.InsetsController}.
+     * {@code true} if the IME has not been mostly hidden via {@link android.view.InsetsController}
      */
-    @GuardedBy("ImfLock.class")
     private boolean mCurPerceptible;
 
     /**
@@ -749,26 +749,33 @@
     SparseArray<AccessibilitySessionState> mEnabledAccessibilitySessions = new SparseArray<>();
 
     /**
-     * {@code true} if the device is currently interactive with the user, initially true.
-     *
-     * @see #handleSetInteractive
+     * True if the device is currently interactive with user.  The value is true initially.
      */
-    @GuardedBy("ImfLock.class")
     boolean mIsInteractive = true;
 
-    @GuardedBy("ImfLock.class")
-    @InputMethodService.BackDispositionMode
     int mBackDisposition = InputMethodService.BACK_DISPOSITION_DEFAULT;
 
     /**
-     * The {@link InputMethodService.ImeWindowVisibility} of the currently bound IME,
-     * or {@code 0} if no IME is bound.
+     * A set of status bits regarding the active IME.
      *
-     * <p><em>Do not update this value outside of {@link #setImeWindowStatus(IBinder, int, int)} and
+     * <p>This value is a combination of following two bits:</p>
+     * <dl>
+     * <dt>{@link InputMethodService#IME_ACTIVE}</dt>
+     * <dd>
+     *   If this bit is ON, connected IME is ready to accept touch/key events.
+     * </dd>
+     * <dt>{@link InputMethodService#IME_VISIBLE}</dt>
+     * <dd>
+     *   If this bit is ON, some of IME view, e.g. software input, candidate view, is visible.
+     * </dd>
+     * <dt>{@link InputMethodService#IME_INVISIBLE}</dt>
+     * <dd> If this bit is ON, IME is ready with views from last EditorInfo but is
+     *    currently invisible.
+     * </dd>
+     * </dl>
+     * <em>Do not update this value outside of {@link #setImeWindowStatus(IBinder, int, int)} and
      * {@link InputMethodBindingController#unbindCurrentMethod()}.</em>
      */
-    @GuardedBy("ImfLock.class")
-    @InputMethodService.ImeWindowVisibility
     int mImeWindowVis;
 
     private LocaleList mLastSystemLocales;
@@ -1529,6 +1536,7 @@
                             // Uh oh, current input method is no longer around!
                             // Pick another one...
                             Slog.i(TAG, "Current input method removed: " + curInputMethodId);
+                            updateSystemUiLocked(0 /* vis */, mBackDisposition);
                             if (!chooseNewDefaultIMELocked()) {
                                 changed = true;
                                 curIm = null;
@@ -2360,6 +2368,28 @@
         }
     }
 
+    /**
+     * Called when {@link #resetCurrentMethodAndClientLocked(int)} invoked for clean-up states
+     * before unbinding the current method.
+     */
+    @GuardedBy("ImfLock.class")
+    void onUnbindCurrentMethodByReset() {
+        final ImeTargetWindowState winState = mVisibilityStateComputer.getWindowStateOrNull(
+                mCurFocusedWindow);
+        if (winState != null && !winState.isRequestedImeVisible()
+                && !mVisibilityStateComputer.isInputShown()) {
+            // Normally, the focus window will apply the IME visibility state to
+            // WindowManager when the IME has applied it. But it would be too late when
+            // switching IMEs in between different users. (Since the focused IME will
+            // first unbind the service to switch to bind the next user of the IME
+            // service, that wouldn't make the attached IME token validity check in time)
+            // As a result, we have to notify WM to apply IME visibility before clearing the
+            // binding states in the first place.
+            mVisibilityApplier.applyImeVisibility(mCurFocusedWindow, mCurStatsToken,
+                    STATE_HIDE_IME);
+        }
+    }
+
     /** {@code true} when a {@link ClientState} has attached from starting the input connection. */
     @GuardedBy("ImfLock.class")
     boolean hasAttachedClient() {
@@ -2831,6 +2861,8 @@
     @GuardedBy("ImfLock.class")
     void resetCurrentMethodAndClientLocked(@UnbindReason int unbindClientReason) {
         setSelectedMethodIdLocked(null);
+        // Callback before clean-up binding states.
+        onUnbindCurrentMethodByReset();
         mBindingController.unbindCurrentMethod();
         unbindCurrentClientLocked(unbindClientReason);
     }
@@ -2931,6 +2963,7 @@
                     sessionState.mSession.finishSession();
                 } catch (RemoteException e) {
                     Slog.w(TAG, "Session failed to close due to remote exception", e);
+                    updateSystemUiLocked(0 /* vis */, mBackDisposition);
                 }
                 sessionState.mSession = null;
             }
@@ -3040,8 +3073,7 @@
     }
 
     @GuardedBy("ImfLock.class")
-    private boolean shouldShowImeSwitcherLocked(
-            @InputMethodService.ImeWindowVisibility int visibility) {
+    private boolean shouldShowImeSwitcherLocked(int visibility) {
         if (!mShowOngoingImeSwitcherForPhones) return false;
         // When the IME switcher dialog is shown, the IME switcher button should be hidden.
         if (mMenuController.getSwitchingDialogLocked() != null) return false;
@@ -3053,7 +3085,8 @@
                 && mWindowManagerInternal.isKeyguardSecure(mSettings.getCurrentUserId())) {
             return false;
         }
-        if ((visibility & InputMethodService.IME_ACTIVE) == 0) {
+        if ((visibility & InputMethodService.IME_ACTIVE) == 0
+                || (visibility & InputMethodService.IME_INVISIBLE) != 0) {
             return false;
         }
         if (mWindowManagerInternal.isHardKeyboardAvailable()) {
@@ -3112,9 +3145,7 @@
 
     @BinderThread
     @SuppressWarnings("deprecation")
-    private void setImeWindowStatus(@NonNull IBinder token,
-            @InputMethodService.ImeWindowVisibility int vis,
-            @InputMethodService.BackDispositionMode int backDisposition) {
+    private void setImeWindowStatus(@NonNull IBinder token, int vis, int backDisposition) {
         final int topFocusedDisplayId = mWindowManagerInternal.getTopFocusedDisplayId();
 
         synchronized (ImfLock.class) {
@@ -3131,7 +3162,7 @@
             }
             mImeWindowVis = vis;
             mBackDisposition = backDisposition;
-            updateSystemUiLocked(mImeWindowVis, mBackDisposition);
+            updateSystemUiLocked(vis, backDisposition);
         }
 
         final boolean dismissImeOnBackKeyPressed;
@@ -3166,46 +3197,37 @@
 
     private void updateImeWindowStatus(boolean disableImeIcon) {
         synchronized (ImfLock.class) {
-            // TODO(b/285109020): disableImeIcon should be stored in a property like
-            //  mIsSwitcherIconDisabled, but it is currently not reliably cleared.
-            updateSystemUiLocked(disableImeIcon ? 0 : mImeWindowVis, mBackDisposition);
+            if (disableImeIcon) {
+                updateSystemUiLocked(0, mBackDisposition);
+            } else {
+                updateSystemUiLocked();
+            }
         }
     }
 
     @GuardedBy("ImfLock.class")
     void updateSystemUiLocked() {
-        // This is only used by InputMethodMenuController to trigger the IME switcher icon
-        // visibility, by having {@code shouldShowImeSwitcherLocked} called, which depends on the
-        // visibility of the IME switcher dialog.
         updateSystemUiLocked(mImeWindowVis, mBackDisposition);
     }
 
     // Caution! This method is called in this class. Handle multi-user carefully
     @GuardedBy("ImfLock.class")
-    private void updateSystemUiLocked(@InputMethodService.ImeWindowVisibility int vis,
-            @InputMethodService.BackDispositionMode int backDisposition) {
+    private void updateSystemUiLocked(int vis, int backDisposition) {
         if (getCurTokenLocked() == null) {
             return;
         }
         if (DEBUG) {
             Slog.d(TAG, "IME window vis: " + vis
-                    + " active: " + ((vis & InputMethodService.IME_ACTIVE) != 0)
-                    + " visible: " + ((vis & InputMethodService.IME_VISIBLE) != 0)
-                    + " backDisposition: " + backDisposition
-                    + " isInteractive: " + mIsInteractive
-                    + " curPerceptible: " + mCurPerceptible
+                    + " active: " + (vis & InputMethodService.IME_ACTIVE)
+                    + " inv: " + (vis & InputMethodService.IME_INVISIBLE)
                     + " displayId: " + mCurTokenDisplayId);
         }
 
         // TODO: Move this clearing calling identity block to setImeWindowStatus after making sure
-        //  all updateSystemUi happens on system privilege.
+        // all updateSystemUi happens on system privilege.
         final long ident = Binder.clearCallingIdentity();
         try {
-            if (!mIsInteractive) {
-                // When we are not interactive,
-                // the visibility should be 0 (no IME icons should be shown).
-                vis = 0;
-            } else if (!mCurPerceptible) {
+            if (!mCurPerceptible) {
                 if ((vis & InputMethodService.IME_VISIBLE) != 0) {
                     vis &= ~InputMethodService.IME_VISIBLE;
                     vis |= InputMethodService.IME_VISIBLE_IMPERCEPTIBLE;
@@ -3540,7 +3562,7 @@
                     return;
                 }
                 mCurPerceptible = perceptible;
-                updateSystemUiLocked(mImeWindowVis, mBackDisposition);
+                updateSystemUiLocked();
             }
         });
     }
@@ -5102,11 +5124,8 @@
 
     private void handleSetInteractive(final boolean interactive) {
         synchronized (ImfLock.class) {
-            if (mIsInteractive == interactive) {
-                return;
-            }
             mIsInteractive = interactive;
-            updateSystemUiLocked(mImeWindowVis, mBackDisposition);
+            updateSystemUiLocked(interactive ? mImeWindowVis : 0, mBackDisposition);
 
             // Inform the current client of the change in active status
             if (mCurClient == null || mCurClient.mClient == null) {
@@ -6741,8 +6760,7 @@
 
         @BinderThread
         @Override
-        public void setImeWindowStatusAsync(@InputMethodService.ImeWindowVisibility int vis,
-                @InputMethodService.BackDispositionMode int backDisposition) {
+        public void setImeWindowStatusAsync(int vis, int backDisposition) {
             mImms.setImeWindowStatus(mToken, vis, backDisposition);
         }
 
diff --git a/services/core/java/com/android/server/notification/NotificationRecord.java b/services/core/java/com/android/server/notification/NotificationRecord.java
index 0292a99..b2d3fca 100644
--- a/services/core/java/com/android/server/notification/NotificationRecord.java
+++ b/services/core/java/com/android/server/notification/NotificationRecord.java
@@ -103,7 +103,7 @@
     final int mTargetSdkVersion;
     final int mOriginalFlags;
     private final Context mContext;
-    private final KeyguardManager mKeyguardManager;
+    private KeyguardManager mKeyguardManager;
     private final PowerManager mPowerManager;
     NotificationUsageStats.SingleNotificationStats stats;
     boolean isCanceled;
@@ -1625,10 +1625,21 @@
     }
 
     boolean isLocked() {
-        return mKeyguardManager.isKeyguardLocked()
+        return getKeyguardManager().isKeyguardLocked()
                 || !mPowerManager.isInteractive();  // Unlocked AOD
     }
 
+    /**
+     * For some early {@link NotificationRecord}, {@link KeyguardManager} can be {@code null} in
+     * the constructor. Retrieve it again if it is null.
+     */
+    private KeyguardManager getKeyguardManager() {
+        if (mKeyguardManager == null) {
+            mKeyguardManager = mContext.getSystemService(KeyguardManager.class);
+        }
+        return mKeyguardManager;
+    }
+
     @VisibleForTesting
     static final class Light {
         public final int color;
diff --git a/services/core/java/com/android/server/pm/PackageSetting.java b/services/core/java/com/android/server/pm/PackageSetting.java
index 7fda092..2411820 100644
--- a/services/core/java/com/android/server/pm/PackageSetting.java
+++ b/services/core/java/com/android/server/pm/PackageSetting.java
@@ -337,7 +337,7 @@
             final long previousFirstInstallTime =
                     replacedPkgSetting.getUserStateOrDefault(userId).getFirstInstallTimeMillis();
             if (previousFirstInstallTime != 0) {
-                modifyUserState(userId).setFirstInstallTime(previousFirstInstallTime);
+                modifyUserState(userId).setFirstInstallTimeMillis(previousFirstInstallTime);
             }
         }
         onChanged();
@@ -352,10 +352,10 @@
         if (userId == UserHandle.USER_ALL) {
             int userStateCount = mUserStates.size();
             for (int i = 0; i < userStateCount; i++) {
-                mUserStates.valueAt(i).setFirstInstallTime(firstInstallTime);
+                mUserStates.valueAt(i).setFirstInstallTimeMillis(firstInstallTime);
             }
         } else {
-            modifyUserState(userId).setFirstInstallTime(firstInstallTime);
+            modifyUserState(userId).setFirstInstallTimeMillis(firstInstallTime);
         }
         onChanged();
         return this;
@@ -894,7 +894,7 @@
                 .setVirtualPreload(virtualPreload)
                 .setHarmfulAppWarning(harmfulAppWarning)
                 .setSplashScreenTheme(splashScreenTheme)
-                .setFirstInstallTime(firstInstallTime);
+                .setFirstInstallTimeMillis(firstInstallTime);
         onChanged();
     }
 
diff --git a/services/core/java/com/android/server/pm/pkg/PackageUserStateImpl.java b/services/core/java/com/android/server/pm/pkg/PackageUserStateImpl.java
index ed4aab9..e8e2d41 100644
--- a/services/core/java/com/android/server/pm/pkg/PackageUserStateImpl.java
+++ b/services/core/java/com/android/server/pm/pkg/PackageUserStateImpl.java
@@ -16,6 +16,7 @@
 
 package com.android.server.pm.pkg;
 
+import android.annotation.CurrentTimeMillisLong;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.content.ComponentName;
@@ -61,6 +62,7 @@
     private int mDistractionFlags;
     private boolean mInstantApp;
     private boolean mVirtualPreload;
+    @PackageManager.EnabledState
     private int mEnabledState = PackageManager.COMPONENT_ENABLED_STATE_DEFAULT;
     @PackageManager.InstallReason
     private int mInstallReason = PackageManager.INSTALL_REASON_UNKNOWN;
@@ -90,7 +92,7 @@
     @Nullable
     private WatchedArrayMap<ComponentName, Pair<String, Integer>> mComponentLabelIconOverrideMap;
 
-    private long mFirstInstallTime;
+    private @CurrentTimeMillisLong long mFirstInstallTimeMillis;
 
     // TODO(b/239050028): Remove, enforce notifying parent through PMS commit method
     @Nullable
@@ -147,7 +149,7 @@
         mSuspendParams = other.mSuspendParams == null ? null : other.mSuspendParams.snapshot();
         mComponentLabelIconOverrideMap = other.mComponentLabelIconOverrideMap == null
                 ? null : other.mComponentLabelIconOverrideMap.snapshot();
-        mFirstInstallTime = other.mFirstInstallTime;
+        mFirstInstallTimeMillis = other.mFirstInstallTimeMillis;
         mSnapshot = new SnapshotCache.Sealed<>();
     }
 
@@ -538,8 +540,8 @@
         return this;
     }
 
-    public @NonNull PackageUserStateImpl setFirstInstallTime(long value) {
-        mFirstInstallTime = value;
+    public @NonNull PackageUserStateImpl setFirstInstallTimeMillis(long value) {
+        mFirstInstallTimeMillis = value;
         onChanged();
         return this;
     }
@@ -643,7 +645,7 @@
     }
 
     @DataClass.Generated.Member
-    public int getEnabledState() {
+    public @PackageManager.EnabledState int getEnabledState() {
         return mEnabledState;
     }
 
@@ -691,8 +693,8 @@
     }
 
     @DataClass.Generated.Member
-    public long getFirstInstallTimeMillis() {
-        return mFirstInstallTime;
+    public @CurrentTimeMillisLong long getFirstInstallTimeMillis() {
+        return mFirstInstallTimeMillis;
     }
 
     @DataClass.Generated.Member
@@ -766,7 +768,7 @@
                 && Objects.equals(mSplashScreenTheme, that.mSplashScreenTheme)
                 && Objects.equals(mSuspendParams, that.mSuspendParams)
                 && Objects.equals(mComponentLabelIconOverrideMap, that.mComponentLabelIconOverrideMap)
-                && mFirstInstallTime == that.mFirstInstallTime
+                && mFirstInstallTimeMillis == that.mFirstInstallTimeMillis
                 && watchableEquals(that.mWatchable)
                 && snapshotEquals(that.mSnapshot);
     }
@@ -798,17 +800,17 @@
         _hash = 31 * _hash + Objects.hashCode(mSplashScreenTheme);
         _hash = 31 * _hash + Objects.hashCode(mSuspendParams);
         _hash = 31 * _hash + Objects.hashCode(mComponentLabelIconOverrideMap);
-        _hash = 31 * _hash + Long.hashCode(mFirstInstallTime);
+        _hash = 31 * _hash + Long.hashCode(mFirstInstallTimeMillis);
         _hash = 31 * _hash + watchableHashCode();
         _hash = 31 * _hash + snapshotHashCode();
         return _hash;
     }
 
     @DataClass.Generated(
-            time = 1668033772891L,
+            time = 1686952839807L,
             codegenVersion = "1.0.23",
             sourceFile = "frameworks/base/services/core/java/com/android/server/pm/pkg/PackageUserStateImpl.java",
-            inputSignatures = "protected @android.annotation.Nullable com.android.server.utils.WatchedArraySet<java.lang.String> mDisabledComponentsWatched\nprotected @android.annotation.Nullable com.android.server.utils.WatchedArraySet<java.lang.String> mEnabledComponentsWatched\nprivate  long mCeDataInode\nprivate  boolean mInstalled\nprivate  boolean mStopped\nprivate  boolean mNotLaunched\nprivate  boolean mHidden\nprivate  int mDistractionFlags\nprivate  boolean mInstantApp\nprivate  boolean mVirtualPreload\nprivate  int mEnabledState\nprivate @android.content.pm.PackageManager.InstallReason int mInstallReason\nprivate @android.content.pm.PackageManager.UninstallReason int mUninstallReason\nprivate @android.annotation.Nullable java.lang.String mHarmfulAppWarning\nprivate @android.annotation.Nullable java.lang.String mLastDisableAppCaller\nprivate @android.annotation.Nullable android.content.pm.overlay.OverlayPaths mOverlayPaths\nprotected @android.annotation.Nullable com.android.server.utils.WatchedArrayMap<java.lang.String,android.content.pm.overlay.OverlayPaths> mSharedLibraryOverlayPaths\nprivate @android.annotation.Nullable java.lang.String mSplashScreenTheme\nprivate @android.annotation.Nullable com.android.server.utils.WatchedArrayMap<java.lang.String,com.android.server.pm.pkg.SuspendParams> mSuspendParams\nprivate @android.annotation.Nullable com.android.server.utils.WatchedArrayMap<android.content.ComponentName,android.util.Pair<java.lang.String,java.lang.Integer>> mComponentLabelIconOverrideMap\nprivate  long mFirstInstallTime\nprivate @android.annotation.Nullable com.android.server.utils.Watchable mWatchable\nfinal @android.annotation.NonNull com.android.server.utils.SnapshotCache<com.android.server.pm.pkg.PackageUserStateImpl> mSnapshot\nprivate  com.android.server.utils.SnapshotCache<com.android.server.pm.pkg.PackageUserStateImpl> makeCache()\nprivate  void onChanged()\npublic @android.annotation.NonNull @java.lang.Override com.android.server.pm.pkg.PackageUserStateImpl snapshot()\npublic @android.annotation.Nullable boolean setOverlayPaths(android.content.pm.overlay.OverlayPaths)\npublic  boolean setSharedLibraryOverlayPaths(java.lang.String,android.content.pm.overlay.OverlayPaths)\npublic @android.annotation.Nullable @java.lang.Override com.android.server.utils.WatchedArraySet<java.lang.String> getDisabledComponentsNoCopy()\npublic @android.annotation.Nullable @java.lang.Override com.android.server.utils.WatchedArraySet<java.lang.String> getEnabledComponentsNoCopy()\npublic @android.annotation.NonNull @java.lang.Override android.util.ArraySet<java.lang.String> getDisabledComponents()\npublic @android.annotation.NonNull @java.lang.Override android.util.ArraySet<java.lang.String> getEnabledComponents()\npublic @java.lang.Override boolean isComponentEnabled(java.lang.String)\npublic @java.lang.Override boolean isComponentDisabled(java.lang.String)\npublic @java.lang.Override android.content.pm.overlay.OverlayPaths getAllOverlayPaths()\npublic @com.android.internal.annotations.VisibleForTesting boolean overrideLabelAndIcon(android.content.ComponentName,java.lang.String,java.lang.Integer)\npublic  void resetOverrideComponentLabelIcon()\npublic @android.annotation.Nullable android.util.Pair<java.lang.String,java.lang.Integer> getOverrideLabelIconForComponent(android.content.ComponentName)\npublic @java.lang.Override boolean isSuspended()\npublic  com.android.server.pm.pkg.PackageUserStateImpl putSuspendParams(java.lang.String,com.android.server.pm.pkg.SuspendParams)\npublic  com.android.server.pm.pkg.PackageUserStateImpl removeSuspension(java.lang.String)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setDisabledComponents(android.util.ArraySet<java.lang.String>)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setEnabledComponents(android.util.ArraySet<java.lang.String>)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setEnabledComponents(com.android.server.utils.WatchedArraySet<java.lang.String>)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setDisabledComponents(com.android.server.utils.WatchedArraySet<java.lang.String>)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setCeDataInode(long)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setInstalled(boolean)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setStopped(boolean)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setNotLaunched(boolean)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setHidden(boolean)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setDistractionFlags(int)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setInstantApp(boolean)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setVirtualPreload(boolean)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setEnabledState(int)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setInstallReason(int)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setUninstallReason(int)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setHarmfulAppWarning(java.lang.String)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setLastDisableAppCaller(java.lang.String)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setSharedLibraryOverlayPaths(android.util.ArrayMap<java.lang.String,android.content.pm.overlay.OverlayPaths>)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setSplashScreenTheme(java.lang.String)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setSuspendParams(android.util.ArrayMap<java.lang.String,com.android.server.pm.pkg.SuspendParams>)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setComponentLabelIconOverrideMap(android.util.ArrayMap<android.content.ComponentName,android.util.Pair<java.lang.String,java.lang.Integer>>)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setFirstInstallTime(long)\npublic @android.annotation.NonNull @java.lang.Override java.util.Map<java.lang.String,android.content.pm.overlay.OverlayPaths> getSharedLibraryOverlayPaths()\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setWatchable(com.android.server.utils.Watchable)\nprivate  boolean watchableEquals(com.android.server.utils.Watchable)\nprivate  int watchableHashCode()\nprivate  boolean snapshotEquals(com.android.server.utils.SnapshotCache<com.android.server.pm.pkg.PackageUserStateImpl>)\nprivate  int snapshotHashCode()\nclass PackageUserStateImpl extends com.android.server.utils.WatchableImpl implements [com.android.server.pm.pkg.PackageUserStateInternal, com.android.server.utils.Snappable]\n@com.android.internal.util.DataClass(genConstructor=false, genBuilder=false, genEqualsHashCode=true)")
+            inputSignatures = "protected @android.annotation.Nullable com.android.server.utils.WatchedArraySet<java.lang.String> mDisabledComponentsWatched\nprotected @android.annotation.Nullable com.android.server.utils.WatchedArraySet<java.lang.String> mEnabledComponentsWatched\nprivate  long mCeDataInode\nprivate  boolean mInstalled\nprivate  boolean mStopped\nprivate  boolean mNotLaunched\nprivate  boolean mHidden\nprivate  int mDistractionFlags\nprivate  boolean mInstantApp\nprivate  boolean mVirtualPreload\nprivate @android.content.pm.PackageManager.EnabledState int mEnabledState\nprivate @android.content.pm.PackageManager.InstallReason int mInstallReason\nprivate @android.content.pm.PackageManager.UninstallReason int mUninstallReason\nprivate @android.annotation.Nullable java.lang.String mHarmfulAppWarning\nprivate @android.annotation.Nullable java.lang.String mLastDisableAppCaller\nprivate @android.annotation.Nullable android.content.pm.overlay.OverlayPaths mOverlayPaths\nprotected @android.annotation.Nullable com.android.server.utils.WatchedArrayMap<java.lang.String,android.content.pm.overlay.OverlayPaths> mSharedLibraryOverlayPaths\nprivate @android.annotation.Nullable java.lang.String mSplashScreenTheme\nprivate @android.annotation.Nullable com.android.server.utils.WatchedArrayMap<java.lang.String,com.android.server.pm.pkg.SuspendParams> mSuspendParams\nprivate @android.annotation.Nullable com.android.server.utils.WatchedArrayMap<android.content.ComponentName,android.util.Pair<java.lang.String,java.lang.Integer>> mComponentLabelIconOverrideMap\nprivate @android.annotation.CurrentTimeMillisLong long mFirstInstallTimeMillis\nprivate @android.annotation.Nullable com.android.server.utils.Watchable mWatchable\nfinal @android.annotation.NonNull com.android.server.utils.SnapshotCache<com.android.server.pm.pkg.PackageUserStateImpl> mSnapshot\nprivate  com.android.server.utils.SnapshotCache<com.android.server.pm.pkg.PackageUserStateImpl> makeCache()\nprivate  void onChanged()\npublic @android.annotation.NonNull @java.lang.Override com.android.server.pm.pkg.PackageUserStateImpl snapshot()\npublic @android.annotation.Nullable boolean setOverlayPaths(android.content.pm.overlay.OverlayPaths)\npublic  boolean setSharedLibraryOverlayPaths(java.lang.String,android.content.pm.overlay.OverlayPaths)\npublic @android.annotation.Nullable @java.lang.Override com.android.server.utils.WatchedArraySet<java.lang.String> getDisabledComponentsNoCopy()\npublic @android.annotation.Nullable @java.lang.Override com.android.server.utils.WatchedArraySet<java.lang.String> getEnabledComponentsNoCopy()\npublic @android.annotation.NonNull @java.lang.Override android.util.ArraySet<java.lang.String> getDisabledComponents()\npublic @android.annotation.NonNull @java.lang.Override android.util.ArraySet<java.lang.String> getEnabledComponents()\npublic @java.lang.Override boolean isComponentEnabled(java.lang.String)\npublic @java.lang.Override boolean isComponentDisabled(java.lang.String)\npublic @java.lang.Override android.content.pm.overlay.OverlayPaths getAllOverlayPaths()\npublic @com.android.internal.annotations.VisibleForTesting boolean overrideLabelAndIcon(android.content.ComponentName,java.lang.String,java.lang.Integer)\npublic  void resetOverrideComponentLabelIcon()\npublic @android.annotation.Nullable android.util.Pair<java.lang.String,java.lang.Integer> getOverrideLabelIconForComponent(android.content.ComponentName)\npublic @java.lang.Override boolean isSuspended()\npublic  com.android.server.pm.pkg.PackageUserStateImpl putSuspendParams(java.lang.String,com.android.server.pm.pkg.SuspendParams)\npublic  com.android.server.pm.pkg.PackageUserStateImpl removeSuspension(java.lang.String)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setDisabledComponents(android.util.ArraySet<java.lang.String>)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setEnabledComponents(android.util.ArraySet<java.lang.String>)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setEnabledComponents(com.android.server.utils.WatchedArraySet<java.lang.String>)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setDisabledComponents(com.android.server.utils.WatchedArraySet<java.lang.String>)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setCeDataInode(long)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setInstalled(boolean)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setStopped(boolean)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setNotLaunched(boolean)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setHidden(boolean)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setDistractionFlags(int)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setInstantApp(boolean)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setVirtualPreload(boolean)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setEnabledState(int)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setInstallReason(int)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setUninstallReason(int)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setHarmfulAppWarning(java.lang.String)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setLastDisableAppCaller(java.lang.String)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setSharedLibraryOverlayPaths(android.util.ArrayMap<java.lang.String,android.content.pm.overlay.OverlayPaths>)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setSplashScreenTheme(java.lang.String)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setSuspendParams(android.util.ArrayMap<java.lang.String,com.android.server.pm.pkg.SuspendParams>)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setComponentLabelIconOverrideMap(android.util.ArrayMap<android.content.ComponentName,android.util.Pair<java.lang.String,java.lang.Integer>>)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setFirstInstallTimeMillis(long)\npublic @android.annotation.NonNull @java.lang.Override java.util.Map<java.lang.String,android.content.pm.overlay.OverlayPaths> getSharedLibraryOverlayPaths()\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setWatchable(com.android.server.utils.Watchable)\nprivate  boolean watchableEquals(com.android.server.utils.Watchable)\nprivate  int watchableHashCode()\nprivate  boolean snapshotEquals(com.android.server.utils.SnapshotCache<com.android.server.pm.pkg.PackageUserStateImpl>)\nprivate  int snapshotHashCode()\nclass PackageUserStateImpl extends com.android.server.utils.WatchableImpl implements [com.android.server.pm.pkg.PackageUserStateInternal, com.android.server.utils.Snappable]\n@com.android.internal.util.DataClass(genConstructor=false, genBuilder=false, genEqualsHashCode=true)")
     @Deprecated
     private void __metadata() {}
 
diff --git a/services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java b/services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java
index 6e9a22c..efd8b6d 100644
--- a/services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java
+++ b/services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java
@@ -19,7 +19,6 @@
 import android.annotation.Nullable;
 import android.app.ITransientNotificationCallback;
 import android.hardware.fingerprint.IUdfpsRefreshRateRequestCallback;
-import android.inputmethodservice.InputMethodService;
 import android.os.Bundle;
 import android.os.IBinder;
 import android.view.WindowInsets.Type.InsetsType;
@@ -55,13 +54,13 @@
      * @param displayId The display to which the IME is bound to.
      * @param token The IME token.
      * @param vis Bit flags about the IME visibility.
+     *            (e.g. {@link android.inputmethodservice.InputMethodService#IME_ACTIVE})
      * @param backDisposition Bit flags about the IME back disposition.
+     *         (e.g. {@link android.inputmethodservice.InputMethodService#BACK_DISPOSITION_DEFAULT})
      * @param showImeSwitcher {@code true} when the IME switcher button should be shown.
      */
-    void setImeWindowStatus(int displayId, IBinder token,
-            @InputMethodService.ImeWindowVisibility int vis,
-            @InputMethodService.BackDispositionMode int backDisposition,
-            boolean showImeSwitcher);
+    void setImeWindowStatus(int displayId, IBinder token, int vis,
+            int backDisposition, boolean showImeSwitcher);
 
     /**
      * See {@link android.app.StatusBarManager#setIcon(String, int, int, String)}.
diff --git a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
index 719b2d2..cc849b6 100644
--- a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
+++ b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
@@ -59,7 +59,6 @@
 import android.hardware.display.DisplayManager;
 import android.hardware.display.DisplayManager.DisplayListener;
 import android.hardware.fingerprint.IUdfpsRefreshRateRequestCallback;
-import android.inputmethodservice.InputMethodService;
 import android.media.INearbyMediaDevicesProvider;
 import android.media.MediaRoute2Info;
 import android.net.Uri;
@@ -347,9 +346,10 @@
 
         @Override
         public void showScreenPinningRequest(int taskId) {
-            if (mBar != null) {
+            IStatusBar bar = mBar;
+            if (bar != null) {
                 try {
-                    mBar.showScreenPinningRequest(taskId);
+                    bar.showScreenPinningRequest(taskId);
                 } catch (RemoteException e) {
                 }
             }
@@ -357,9 +357,10 @@
 
         @Override
         public void showAssistDisclosure() {
-            if (mBar != null) {
+            IStatusBar bar = mBar;
+            if (bar != null) {
                 try {
-                    mBar.showAssistDisclosure();
+                    bar.showAssistDisclosure();
                 } catch (RemoteException e) {
                 }
             }
@@ -367,9 +368,10 @@
 
         @Override
         public void startAssist(Bundle args) {
-            if (mBar != null) {
+            IStatusBar bar = mBar;
+            if (bar != null) {
                 try {
-                    mBar.startAssist(args);
+                    bar.startAssist(args);
                 } catch (RemoteException e) {
                 }
             }
@@ -377,9 +379,10 @@
 
         @Override
         public void onCameraLaunchGestureDetected(int source) {
-            if (mBar != null) {
+            IStatusBar bar = mBar;
+            if (bar != null) {
                 try {
-                    mBar.onCameraLaunchGestureDetected(source);
+                    bar.onCameraLaunchGestureDetected(source);
                 } catch (RemoteException e) {
                 }
             }
@@ -393,9 +396,10 @@
         @Override
         public void onEmergencyActionLaunchGestureDetected() {
             if (SPEW) Slog.d(TAG, "Launching emergency action");
-            if (mBar != null) {
+            IStatusBar bar = mBar;
+            if (bar != null) {
                 try {
-                    mBar.onEmergencyActionLaunchGestureDetected();
+                    bar.onEmergencyActionLaunchGestureDetected();
                 } catch (RemoteException e) {
                     if (SPEW) Slog.d(TAG, "Failed to launch emergency action");
                 }
@@ -410,9 +414,10 @@
         @Override
         public void toggleSplitScreen() {
             enforceStatusBarService();
-            if (mBar != null) {
+            IStatusBar bar = mBar;
+            if (bar != null) {
                 try {
-                    mBar.toggleSplitScreen();
+                    bar.toggleSplitScreen();
                 } catch (RemoteException ex) {}
             }
         }
@@ -420,27 +425,30 @@
         @Override
         public void appTransitionFinished(int displayId) {
             enforceStatusBarService();
-            if (mBar != null) {
+            IStatusBar bar = mBar;
+            if (bar != null) {
                 try {
-                    mBar.appTransitionFinished(displayId);
+                    bar.appTransitionFinished(displayId);
                 } catch (RemoteException ex) {}
             }
         }
 
         @Override
         public void toggleTaskbar() {
-            if (mBar != null) {
+            IStatusBar bar = mBar;
+            if (bar != null) {
                 try {
-                    mBar.toggleTaskbar();
+                    bar.toggleTaskbar();
                 } catch (RemoteException ex) {}
             }
         }
 
         @Override
         public void toggleRecentApps() {
-            if (mBar != null) {
+            IStatusBar bar = mBar;
+            if (bar != null) {
                 try {
-                    mBar.toggleRecentApps();
+                    bar.toggleRecentApps();
                 } catch (RemoteException ex) {}
             }
         }
@@ -454,45 +462,50 @@
 
         @Override
         public void preloadRecentApps() {
-            if (mBar != null) {
+            IStatusBar bar = mBar;
+            if (bar != null) {
                 try {
-                    mBar.preloadRecentApps();
+                    bar.preloadRecentApps();
                 } catch (RemoteException ex) {}
             }
         }
 
         @Override
         public void cancelPreloadRecentApps() {
-            if (mBar != null) {
+            IStatusBar bar = mBar;
+            if (bar != null) {
                 try {
-                    mBar.cancelPreloadRecentApps();
+                    bar.cancelPreloadRecentApps();
                 } catch (RemoteException ex) {}
             }
         }
 
         @Override
         public void showRecentApps(boolean triggeredFromAltTab) {
-            if (mBar != null) {
+            IStatusBar bar = mBar;
+            if (bar != null) {
                 try {
-                    mBar.showRecentApps(triggeredFromAltTab);
+                    bar.showRecentApps(triggeredFromAltTab);
                 } catch (RemoteException ex) {}
             }
         }
 
         @Override
         public void hideRecentApps(boolean triggeredFromAltTab, boolean triggeredFromHomeKey) {
-            if (mBar != null) {
+            IStatusBar bar = mBar;
+            if (bar != null) {
                 try {
-                    mBar.hideRecentApps(triggeredFromAltTab, triggeredFromHomeKey);
+                    bar.hideRecentApps(triggeredFromAltTab, triggeredFromHomeKey);
                 } catch (RemoteException ex) {}
             }
         }
 
         @Override
         public void collapsePanels() {
-            if (mBar != null) {
+            IStatusBar bar = mBar;
+            if (bar != null) {
                 try {
-                    mBar.animateCollapsePanels();
+                    bar.animateCollapsePanels();
                 } catch (RemoteException ex) {
                 }
             }
@@ -500,26 +513,26 @@
 
         @Override
         public void dismissKeyboardShortcutsMenu() {
-            if (mBar != null) {
+            IStatusBar bar = mBar;
+            if (bar != null) {
                 try {
-                    mBar.dismissKeyboardShortcutsMenu();
+                    bar.dismissKeyboardShortcutsMenu();
                 } catch (RemoteException ex) {}
             }
         }
 
         @Override
         public void toggleKeyboardShortcutsMenu(int deviceId) {
-            if (mBar != null) {
+            IStatusBar bar = mBar;
+            if (bar != null) {
                 try {
-                    mBar.toggleKeyboardShortcutsMenu(deviceId);
+                    bar.toggleKeyboardShortcutsMenu(deviceId);
                 } catch (RemoteException ex) {}
             }
         }
 
         @Override
-        public void setImeWindowStatus(int displayId, IBinder token,
-                @InputMethodService.ImeWindowVisibility int vis,
-                @InputMethodService.BackDispositionMode int backDisposition,
+        public void setImeWindowStatus(int displayId, IBinder token, int vis, int backDisposition,
                 boolean showImeSwitcher) {
             StatusBarManagerService.this.setImeWindowStatus(displayId, token, vis, backDisposition,
                     showImeSwitcher);
@@ -539,9 +552,10 @@
 
         @Override
         public void showChargingAnimation(int batteryLevel) {
-            if (mBar != null) {
+            IStatusBar bar = mBar;
+            if (bar != null) {
                 try {
-                    mBar.showWirelessChargingAnimation(batteryLevel);
+                    bar.showWirelessChargingAnimation(batteryLevel);
                 } catch (RemoteException ex){
                 }
             }
@@ -549,7 +563,8 @@
 
         @Override
         public void showPictureInPictureMenu() {
-            if (mBar != null) {
+            IStatusBar bar = mBar;
+            if (bar != null) {
                 try {
                     mBar.showPictureInPictureMenu();
                 } catch (RemoteException ex) {}
@@ -558,27 +573,30 @@
 
         @Override
         public void setWindowState(int displayId, int window, int state) {
-            if (mBar != null) {
+            IStatusBar bar = mBar;
+            if (bar != null) {
                 try {
-                    mBar.setWindowState(displayId, window, state);
+                    bar.setWindowState(displayId, window, state);
                 } catch (RemoteException ex) {}
             }
         }
 
         @Override
         public void appTransitionPending(int displayId) {
-            if (mBar != null) {
+            IStatusBar bar = mBar;
+            if (bar != null) {
                 try {
-                    mBar.appTransitionPending(displayId);
+                    bar.appTransitionPending(displayId);
                 } catch (RemoteException ex) {}
             }
         }
 
         @Override
         public void appTransitionCancelled(int displayId) {
-            if (mBar != null) {
+            IStatusBar bar = mBar;
+            if (bar != null) {
                 try {
-                    mBar.appTransitionCancelled(displayId);
+                    bar.appTransitionCancelled(displayId);
                 } catch (RemoteException ex) {}
             }
         }
@@ -586,9 +604,10 @@
         @Override
         public void appTransitionStarting(int displayId, long statusBarAnimationsStartTime,
                 long statusBarAnimationsDuration) {
-            if (mBar != null) {
+            IStatusBar bar = mBar;
+            if (bar != null) {
                 try {
-                    mBar.appTransitionStarting(
+                    bar.appTransitionStarting(
                             displayId, statusBarAnimationsStartTime, statusBarAnimationsDuration);
                 } catch (RemoteException ex) {}
             }
@@ -596,9 +615,10 @@
 
         @Override
         public void setTopAppHidesStatusBar(boolean hidesStatusBar) {
-            if (mBar != null) {
+            IStatusBar bar = mBar;
+            if (bar != null) {
                 try {
-                    mBar.setTopAppHidesStatusBar(hidesStatusBar);
+                    bar.setTopAppHidesStatusBar(hidesStatusBar);
                 } catch (RemoteException ex) {}
             }
         }
@@ -608,9 +628,10 @@
             if (!mContext.getResources().getBoolean(R.bool.config_showSysuiShutdown)) {
                 return false;
             }
-            if (mBar != null) {
+            IStatusBar bar = mBar;
+            if (bar != null) {
                 try {
-                    mBar.showShutdownUi(isReboot, reason);
+                    bar.showShutdownUi(isReboot, reason);
                     return true;
                 } catch (RemoteException ex) {}
             }
@@ -629,18 +650,20 @@
 
         @Override
         public void onDisplayReady(int displayId) {
-            if (mBar != null) {
+            IStatusBar bar = mBar;
+            if (bar != null) {
                 try {
-                    mBar.onDisplayReady(displayId);
+                    bar.onDisplayReady(displayId);
                 } catch (RemoteException ex) {}
             }
         }
 
         @Override
         public void onRecentsAnimationStateChanged(boolean running) {
-            if (mBar != null) {
+            IStatusBar bar = mBar;
+            if (bar != null) {
                 try {
-                    mBar.onRecentsAnimationStateChanged(running);
+                    bar.onRecentsAnimationStateChanged(running);
                 } catch (RemoteException ex) {}
             }
 
@@ -654,9 +677,10 @@
             getUiState(displayId).setBarAttributes(appearance, appearanceRegions,
                     navbarColorManagedByIme, behavior, requestedVisibleTypes, packageName,
                     letterboxDetails);
-            if (mBar != null) {
+            IStatusBar bar = mBar;
+            if (bar != null) {
                 try {
-                    mBar.onSystemBarAttributesChanged(displayId, appearance, appearanceRegions,
+                    bar.onSystemBarAttributesChanged(displayId, appearance, appearanceRegions,
                             navbarColorManagedByIme, behavior, requestedVisibleTypes, packageName,
                             letterboxDetails);
                 } catch (RemoteException ex) { }
@@ -667,9 +691,10 @@
         public void showTransient(int displayId, @InsetsType int types,
                 boolean isGestureOnSystemBar) {
             getUiState(displayId).showTransient(types);
-            if (mBar != null) {
+            IStatusBar bar = mBar;
+            if (bar != null) {
                 try {
-                    mBar.showTransient(displayId, types, isGestureOnSystemBar);
+                    bar.showTransient(displayId, types, isGestureOnSystemBar);
                 } catch (RemoteException ex) { }
             }
         }
@@ -677,9 +702,10 @@
         @Override
         public void abortTransient(int displayId, @InsetsType int types) {
             getUiState(displayId).clearTransient(types);
-            if (mBar != null) {
+            IStatusBar bar = mBar;
+            if (bar != null) {
                 try {
-                    mBar.abortTransient(displayId, types);
+                    bar.abortTransient(displayId, types);
                 } catch (RemoteException ex) { }
             }
         }
@@ -688,9 +714,10 @@
         public void showToast(int uid, String packageName, IBinder token, CharSequence text,
                 IBinder windowToken, int duration,
                 @Nullable ITransientNotificationCallback callback, int displayId) {
-            if (mBar != null) {
+            IStatusBar bar = mBar;
+            if (bar != null) {
                 try {
-                    mBar.showToast(uid, packageName, token, text, windowToken, duration, callback,
+                    bar.showToast(uid, packageName, token, text, windowToken, duration, callback,
                             displayId);
                 } catch (RemoteException ex) { }
             }
@@ -698,18 +725,20 @@
 
         @Override
         public void hideToast(String packageName, IBinder token) {
-            if (mBar != null) {
+            IStatusBar bar = mBar;
+            if (bar != null) {
                 try {
-                    mBar.hideToast(packageName, token);
+                    bar.hideToast(packageName, token);
                 } catch (RemoteException ex) { }
             }
         }
 
         @Override
         public boolean requestWindowMagnificationConnection(boolean request) {
-            if (mBar != null) {
+            IStatusBar bar = mBar;
+            if (bar != null) {
                 try {
-                    mBar.requestWindowMagnificationConnection(request);
+                    bar.requestWindowMagnificationConnection(request);
                     return true;
                 } catch (RemoteException ex) { }
             }
@@ -718,9 +747,10 @@
 
         @Override
         public void setNavigationBarLumaSamplingEnabled(int displayId, boolean enable) {
-            if (mBar != null) {
+            IStatusBar bar = mBar;
+            if (bar != null) {
                 try {
-                    mBar.setNavigationBarLumaSamplingEnabled(displayId, enable);
+                    bar.setNavigationBarLumaSamplingEnabled(displayId, enable);
                 } catch (RemoteException ex) { }
             }
         }
@@ -730,45 +760,50 @@
             synchronized (mLock) {
                 mUdfpsRefreshRateRequestCallback = callback;
             }
-            if (mBar != null) {
+            IStatusBar bar = mBar;
+            if (bar != null) {
                 try {
-                    mBar.setUdfpsRefreshRateCallback(callback);
+                    bar.setUdfpsRefreshRateCallback(callback);
                 } catch (RemoteException ex) { }
             }
         }
 
         @Override
         public void showRearDisplayDialog(int currentBaseState) {
-            if (mBar != null) {
+            IStatusBar bar = mBar;
+            if (bar != null) {
                 try {
-                    mBar.showRearDisplayDialog(currentBaseState);
+                    bar.showRearDisplayDialog(currentBaseState);
                 } catch (RemoteException ex) { }
             }
         }
 
         @Override
         public void goToFullscreenFromSplit() {
-            if (mBar != null) {
+            IStatusBar bar = mBar;
+            if (bar != null) {
                 try {
-                    mBar.goToFullscreenFromSplit();
+                    bar.goToFullscreenFromSplit();
                 } catch (RemoteException ex) { }
             }
         }
 
         @Override
         public void enterStageSplitFromRunningApp(boolean leftOrTop) {
-            if (mBar != null) {
+            IStatusBar bar = mBar;
+            if (bar != null) {
                 try {
-                    mBar.enterStageSplitFromRunningApp(leftOrTop);
+                    bar.enterStageSplitFromRunningApp(leftOrTop);
                 } catch (RemoteException ex) { }
             }
         }
 
         @Override
         public void showMediaOutputSwitcher(String packageName) {
-            if (mBar != null) {
+            IStatusBar bar = mBar;
+            if (bar != null) {
                 try {
-                    mBar.showMediaOutputSwitcher(packageName);
+                    bar.showMediaOutputSwitcher(packageName);
                 } catch (RemoteException ex) {
                 }
             }
@@ -791,9 +826,10 @@
 
         @Override
         public void showGlobalActions() {
-            if (mBar != null) {
+            IStatusBar bar = mBar;
+            if (bar != null) {
                 try {
-                    mBar.showGlobalActionsMenu();
+                    bar.showGlobalActionsMenu();
                 } catch (RemoteException ex) {}
             }
         }
@@ -1116,9 +1152,10 @@
         if (!state.disableEquals(net1, net2)) {
             state.setDisabled(net1, net2);
             mHandler.post(() -> mNotificationDelegate.onSetDisabled(net1));
-            if (mBar != null) {
+            IStatusBar bar = mBar;
+            if (bar != null) {
                 try {
-                    mBar.disable(displayId, net1, net2);
+                    bar.disable(displayId, net1, net2);
                 } catch (RemoteException ex) {
                 }
             }
@@ -1176,9 +1213,10 @@
             //Slog.d(TAG, "setIcon slot=" + slot + " index=" + index + " icon=" + icon);
             mIcons.put(slot, icon);
 
-            if (mBar != null) {
+            IStatusBar bar = mBar;
+            if (bar != null) {
                 try {
-                    mBar.setIcon(slot, icon);
+                    bar.setIcon(slot, icon);
                 } catch (RemoteException ex) {
                 }
             }
@@ -1197,9 +1235,10 @@
             if (icon.visible != visibility) {
                 icon.visible = visibility;
 
-                if (mBar != null) {
+                IStatusBar bar = mBar;
+                if (bar != null) {
                     try {
-                        mBar.setIcon(slot, icon);
+                        bar.setIcon(slot, icon);
                     } catch (RemoteException ex) {
                     }
                 }
@@ -1214,9 +1253,10 @@
         synchronized (mIcons) {
             mIcons.remove(slot);
 
-            if (mBar != null) {
+            IStatusBar bar = mBar;
+            if (bar != null) {
                 try {
-                    mBar.removeIcon(slot);
+                    bar.removeIcon(slot);
                 } catch (RemoteException ex) {
                 }
             }
@@ -1224,14 +1264,12 @@
     }
 
     @Override
-    public void setImeWindowStatus(int displayId, final IBinder token,
-            @InputMethodService.ImeWindowVisibility final int vis,
-            @InputMethodService.BackDispositionMode final int backDisposition,
-            final boolean showImeSwitcher) {
+    public void setImeWindowStatus(int displayId, final IBinder token, final int vis,
+            final int backDisposition, final boolean showImeSwitcher) {
         enforceStatusBar();
 
         if (SPEW) {
-            Slog.d(TAG, "setImeWindowStatus vis=" + vis + " backDisposition=" + backDisposition);
+            Slog.d(TAG, "swetImeWindowStatus vis=" + vis + " backDisposition=" + backDisposition);
         }
 
         synchronized(mLock) {
@@ -1294,9 +1332,7 @@
         private String mPackageName = "none";
         private int mDisabled1 = 0;
         private int mDisabled2 = 0;
-        @InputMethodService.ImeWindowVisibility
         private int mImeWindowVis = 0;
-        @InputMethodService.BackDispositionMode
         private int mImeBackDisposition = 0;
         private boolean mShowImeSwitcher = false;
         private IBinder mImeToken = null;
@@ -1341,8 +1377,7 @@
             return mDisabled1 == disabled1 && mDisabled2 == disabled2;
         }
 
-        private void setImeWindowState(@InputMethodService.ImeWindowVisibility final int vis,
-                @InputMethodService.BackDispositionMode final int backDisposition,
+        private void setImeWindowState(final int vis, final int backDisposition,
                 final boolean showImeSwitcher, final IBinder token) {
             mImeWindowVis = vis;
             mImeBackDisposition = backDisposition;
@@ -1823,9 +1858,10 @@
     @Override
     public void showInattentiveSleepWarning() {
         enforceStatusBarService();
-        if (mBar != null) {
+        IStatusBar bar = mBar;
+        if (bar != null) {
             try {
-                mBar.showInattentiveSleepWarning();
+                bar.showInattentiveSleepWarning();
             } catch (RemoteException ex) {
             }
         }
@@ -1834,9 +1870,10 @@
     @Override
     public void dismissInattentiveSleepWarning(boolean animated) {
         enforceStatusBarService();
-        if (mBar != null) {
+        IStatusBar bar = mBar;
+        if (bar != null) {
             try {
-                mBar.dismissInattentiveSleepWarning(animated);
+                bar.dismissInattentiveSleepWarning(animated);
             } catch (RemoteException ex) {
             }
         }
@@ -1845,9 +1882,10 @@
     @Override
     public void suppressAmbientDisplay(boolean suppress) {
         enforceStatusBarService();
-        if (mBar != null) {
+        IStatusBar bar = mBar;
+        if (bar != null) {
             try {
-                mBar.suppressAmbientDisplay(suppress);
+                bar.suppressAmbientDisplay(suppress);
             } catch (RemoteException ex) {
             }
         }
@@ -1921,9 +1959,10 @@
                 }
             }
         }
-        if (mBar != null) {
+        IStatusBar bar = mBar;
+        if (bar != null) {
             try {
-                mBar.requestTileServiceListeningState(componentName);
+                bar.requestTileServiceListeningState(componentName);
             } catch (RemoteException e) {
                 Slog.e(TAG, "requestTileServiceListeningState", e);
             }
@@ -2036,9 +2075,10 @@
 
         CharSequence appName = r.serviceInfo.applicationInfo
                 .loadLabel(mContext.getPackageManager());
-        if (mBar != null) {
+        IStatusBar bar = mBar;
+        if (bar != null) {
             try {
-                mBar.requestAddTile(componentName, appName, label, icon, proxyCallback);
+                bar.requestAddTile(componentName, appName, label, icon, proxyCallback);
                 return;
             } catch (RemoteException e) {
                 Slog.e(TAG, "requestAddTile", e);
@@ -2060,9 +2100,10 @@
 
     private void cancelRequestAddTileInternal(String packageName) {
         clearTileAddRequest(packageName);
-        if (mBar != null) {
+        IStatusBar bar = mBar;
+        if (bar != null) {
             try {
-                mBar.cancelRequestAddTile(packageName);
+                bar.cancelRequestAddTile(packageName);
             } catch (RemoteException e) {
                 Slog.e(TAG, "requestAddTile", e);
             }
@@ -2191,9 +2232,10 @@
             @Nullable IUndoMediaTransferCallback undoCallback
     ) {
         enforceMediaContentControl();
-        if (mBar != null) {
+        IStatusBar bar = mBar;
+        if (bar != null) {
             try {
-                mBar.updateMediaTapToTransferSenderDisplay(displayState, routeInfo, undoCallback);
+                bar.updateMediaTapToTransferSenderDisplay(displayState, routeInfo, undoCallback);
             } catch (RemoteException e) {
                 Slog.e(TAG, "updateMediaTapToTransferSenderDisplay", e);
             }
@@ -2214,9 +2256,10 @@
             @Nullable Icon appIcon,
             @Nullable CharSequence appName) {
         enforceMediaContentControl();
-        if (mBar != null) {
+        IStatusBar bar = mBar;
+        if (bar != null) {
             try {
-                mBar.updateMediaTapToTransferReceiverDisplay(
+                bar.updateMediaTapToTransferReceiverDisplay(
                         displayState, routeInfo, appIcon, appName);
             } catch (RemoteException e) {
                 Slog.e(TAG, "updateMediaTapToTransferReceiverDisplay", e);
@@ -2240,9 +2283,10 @@
             @NonNull INearbyMediaDevicesProvider provider
     ) {
         enforceMediaContentControl();
-        if (mBar != null) {
+        IStatusBar bar = mBar;
+        if (bar != null) {
             try {
-                mBar.registerNearbyMediaDevicesProvider(provider);
+                bar.registerNearbyMediaDevicesProvider(provider);
             } catch (RemoteException e) {
                 Slog.e(TAG, "registerNearbyMediaDevicesProvider", e);
             }
@@ -2265,9 +2309,10 @@
             @NonNull INearbyMediaDevicesProvider provider
     ) {
         enforceMediaContentControl();
-        if (mBar != null) {
+        IStatusBar bar = mBar;
+        if (bar != null) {
             try {
-                mBar.unregisterNearbyMediaDevicesProvider(provider);
+                bar.unregisterNearbyMediaDevicesProvider(provider);
             } catch (RemoteException e) {
                 Slog.e(TAG, "unregisterNearbyMediaDevicesProvider", e);
             }
@@ -2278,9 +2323,10 @@
     @Override
     public void showRearDisplayDialog(int currentState) {
         enforceControlDeviceStatePermission();
-        if (mBar != null) {
+        IStatusBar bar = mBar;
+        if (bar != null) {
             try {
-                mBar.showRearDisplayDialog(currentState);
+                bar.showRearDisplayDialog(currentState);
             } catch (RemoteException e) {
                 Slog.e(TAG, "showRearDisplayDialog", e);
             }
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 788bfbc..0994fa4 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -4560,6 +4560,26 @@
         }
         task.forAllActivities(fromActivity -> {
             if (fromActivity == this) return true;
+            // The snapshot starting window could remove itself when receive resized request without
+            // redraw, so transfer it to a different size activity could only cause flicker.
+            // By schedule remove snapshot starting window, the remove process will happen when
+            // transition ready, transition ready means the app window is drawn.
+            final StartingData tmpStartingData = fromActivity.mStartingData;
+            if (tmpStartingData != null && tmpStartingData.mAssociatedTask == null
+                    && mTransitionController.isCollecting(fromActivity)
+                    && tmpStartingData instanceof SnapshotStartingData) {
+                final Rect fromBounds = fromActivity.getBounds();
+                final Rect myBounds = getBounds();
+                if (!fromBounds.equals(myBounds)) {
+                    // Mark as no animation, so these changes won't merge into playing transition.
+                    if (mTransitionController.inPlayingTransition(fromActivity)) {
+                        mTransitionController.setNoAnimation(this);
+                        mTransitionController.setNoAnimation(fromActivity);
+                    }
+                    fromActivity.removeStartingWindow();
+                    return true;
+                }
+            }
             return !fromActivity.isVisibleRequested() && transferStartingWindow(fromActivity);
         });
     }
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index 4a658d6..10ff3a3 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -546,6 +546,7 @@
      */
     private volatile long mLastStopAppSwitchesTime;
 
+    @GuardedBy("itself")
     private final List<AnrController> mAnrController = new ArrayList<>();
     IActivityController mController = null;
     boolean mControllerIsAMonkey = false;
@@ -733,7 +734,7 @@
     private boolean mShowDialogs = true;
 
     /** Set if we are shutting down the system, similar to sleeping. */
-    boolean mShuttingDown = false;
+    volatile boolean mShuttingDown;
 
     /**
      * We want to hold a wake lock while running a voice interaction session, since
@@ -2298,14 +2299,14 @@
 
     /** Register an {@link AnrController} to control the ANR dialog behavior */
     public void registerAnrController(AnrController controller) {
-        synchronized (mGlobalLock) {
+        synchronized (mAnrController) {
             mAnrController.add(controller);
         }
     }
 
     /** Unregister an {@link AnrController} */
     public void unregisterAnrController(AnrController controller) {
-        synchronized (mGlobalLock) {
+        synchronized (mAnrController) {
             mAnrController.remove(controller);
         }
     }
@@ -2321,7 +2322,7 @@
         }
 
         final ArrayList<AnrController> controllers;
-        synchronized (mGlobalLock) {
+        synchronized (mAnrController) {
             controllers = new ArrayList<>(mAnrController);
         }
 
@@ -6034,15 +6035,13 @@
 
         @Override
         public boolean isShuttingDown() {
-            synchronized (mGlobalLock) {
-                return mShuttingDown;
-            }
+            return mShuttingDown;
         }
 
         @Override
         public boolean shuttingDown(boolean booted, int timeout) {
+            mShuttingDown = true;
             synchronized (mGlobalLock) {
-                mShuttingDown = true;
                 mRootWindowContainer.prepareForShutdown();
                 updateEventDispatchingLocked(booted);
                 notifyTaskPersisterLocked(null, true);
diff --git a/services/core/java/com/android/server/wm/AppWarnings.java b/services/core/java/com/android/server/wm/AppWarnings.java
index f7ccc0d..0273a30 100644
--- a/services/core/java/com/android/server/wm/AppWarnings.java
+++ b/services/core/java/com/android/server/wm/AppWarnings.java
@@ -16,7 +16,9 @@
 
 package com.android.server.wm;
 
+import android.annotation.NonNull;
 import android.annotation.UiThread;
+import android.annotation.WorkerThread;
 import android.app.AlertDialog;
 import android.content.BroadcastReceiver;
 import android.content.ComponentName;
@@ -30,14 +32,18 @@
 import android.os.Looper;
 import android.os.Message;
 import android.os.SystemProperties;
+import android.util.ArrayMap;
+import android.util.ArraySet;
 import android.util.AtomicFile;
 import android.util.DisplayMetrics;
 import android.util.Slog;
 import android.util.Xml;
 
+import com.android.internal.annotations.GuardedBy;
 import com.android.internal.util.ArrayUtils;
 import com.android.modules.utils.TypedXmlPullParser;
 import com.android.modules.utils.TypedXmlSerializer;
+import com.android.server.IoThread;
 
 import org.xmlpull.v1.XmlPullParser;
 import org.xmlpull.v1.XmlPullParserException;
@@ -45,9 +51,7 @@
 import java.io.File;
 import java.io.FileInputStream;
 import java.io.FileOutputStream;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.Map;
+import java.util.concurrent.atomic.AtomicReference;
 
 /**
  * Manages warning dialogs shown during application lifecycle.
@@ -61,11 +65,12 @@
     public static final int FLAG_HIDE_DEPRECATED_SDK = 0x04;
     public static final int FLAG_HIDE_DEPRECATED_ABI = 0x08;
 
-    private final HashMap<String, Integer> mPackageFlags = new HashMap<>();
+    @GuardedBy("mPackageFlags")
+    private final ArrayMap<String, Integer> mPackageFlags = new ArrayMap<>();
 
     private final ActivityTaskManagerService mAtm;
     private final Context mUiContext;
-    private final ConfigHandler mHandler;
+    private final WriteConfigTask mWriteConfigTask;
     private final UiHandler mUiHandler;
     private final AtomicFile mConfigFile;
 
@@ -75,30 +80,20 @@
     private DeprecatedAbiDialog mDeprecatedAbiDialog;
 
     /** @see android.app.ActivityManager#alwaysShowUnsupportedCompileSdkWarning */
-    private HashSet<ComponentName> mAlwaysShowUnsupportedCompileSdkWarningActivities =
-            new HashSet<>();
+    private final ArraySet<ComponentName> mAlwaysShowUnsupportedCompileSdkWarningActivities =
+            new ArraySet<>();
 
     /** @see android.app.ActivityManager#alwaysShowUnsupportedCompileSdkWarning */
     void alwaysShowUnsupportedCompileSdkWarning(ComponentName activity) {
         mAlwaysShowUnsupportedCompileSdkWarningActivities.add(activity);
     }
 
-    /**
-     * Creates a new warning dialog manager.
-     * <p>
-     * <strong>Note:</strong> Must be called from the ActivityManagerService thread.
-     *
-     * @param atm
-     * @param uiContext
-     * @param handler
-     * @param uiHandler
-     * @param systemDir
-     */
+    /** Creates a new warning dialog manager. */
     public AppWarnings(ActivityTaskManagerService atm, Context uiContext, Handler handler,
             Handler uiHandler, File systemDir) {
         mAtm = atm;
         mUiContext = uiContext;
-        mHandler = new ConfigHandler(handler.getLooper());
+        mWriteConfigTask = new WriteConfigTask();
         mUiHandler = new UiHandler(uiHandler.getLooper());
         mConfigFile = new AtomicFile(new File(systemDir, CONFIG_FILE_NAME), "warnings-config");
 
@@ -256,8 +251,9 @@
         mUiHandler.hideDialogsForPackage(name);
 
         synchronized (mPackageFlags) {
-            mPackageFlags.remove(name);
-            mHandler.scheduleWrite();
+            if (mPackageFlags.remove(name) != null) {
+                mWriteConfigTask.schedule();
+            }
         }
     }
 
@@ -425,7 +421,7 @@
                 } else {
                     mPackageFlags.remove(name);
                 }
-                mHandler.scheduleWrite();
+                mWriteConfigTask.schedule();
             }
         }
     }
@@ -556,46 +552,30 @@
         }
     }
 
-    /**
-     * Handles messages on the ActivityTaskManagerService thread.
-     */
-    private final class ConfigHandler extends Handler {
-        private static final int MSG_WRITE = 1;
-
-        private static final int DELAY_MSG_WRITE = 10000;
-
-        public ConfigHandler(Looper looper) {
-            super(looper, null, true);
-        }
+    private final class WriteConfigTask implements Runnable {
+        private static final long WRITE_CONFIG_DELAY_MS = 10000;
+        final AtomicReference<ArrayMap<String, Integer>> mPendingPackageFlags =
+                new AtomicReference<>();
 
         @Override
-        public void handleMessage(Message msg) {
-            switch (msg.what) {
-                case MSG_WRITE:
-                    writeConfigToFileAmsThread();
-                    break;
+        public void run() {
+            final ArrayMap<String, Integer> packageFlags = mPendingPackageFlags.getAndSet(null);
+            if (packageFlags != null) {
+                writeConfigToFile(packageFlags);
             }
         }
 
-        public void scheduleWrite() {
-            removeMessages(MSG_WRITE);
-            sendEmptyMessageDelayed(MSG_WRITE, DELAY_MSG_WRITE);
+        @GuardedBy("mPackageFlags")
+        void schedule() {
+            if (mPendingPackageFlags.getAndSet(new ArrayMap<>(mPackageFlags)) == null) {
+                IoThread.getHandler().postDelayed(this, WRITE_CONFIG_DELAY_MS);
+            }
         }
     }
 
-    /**
-     * Writes the configuration file.
-     * <p>
-     * <strong>Note:</strong> Should be called from the ActivityManagerService thread unless you
-     * don't care where you're doing I/O operations. But you <i>do</i> care, don't you?
-     */
-    private void writeConfigToFileAmsThread() {
-        // Create a shallow copy so that we don't have to synchronize on config.
-        final HashMap<String, Integer> packageFlags;
-        synchronized (mPackageFlags) {
-            packageFlags = new HashMap<>(mPackageFlags);
-        }
-
+    /** Writes the configuration file. */
+    @WorkerThread
+    private void writeConfigToFile(@NonNull ArrayMap<String, Integer> packageFlags) {
         FileOutputStream fos = null;
         try {
             fos = mConfigFile.startWrite();
@@ -605,9 +585,9 @@
             out.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true);
             out.startTag(null, "packages");
 
-            for (Map.Entry<String, Integer> entry : packageFlags.entrySet()) {
-                String pkg = entry.getKey();
-                int mode = entry.getValue();
+            for (int i = 0; i < packageFlags.size(); i++) {
+                final String pkg = packageFlags.keyAt(i);
+                final int mode = packageFlags.valueAt(i);
                 if (mode == 0) {
                     continue;
                 }
diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java
index 54fec3e..d9a954f 100644
--- a/services/core/java/com/android/server/wm/RootWindowContainer.java
+++ b/services/core/java/com/android/server/wm/RootWindowContainer.java
@@ -1061,8 +1061,10 @@
                 displayHasContent = true;
             } else if (displayContent != null &&
                     (!mObscureApplicationContentOnSecondaryDisplays
+                            || displayContent.isKeyguardAlwaysUnlocked()
                             || (obscured && type == TYPE_KEYGUARD_DIALOG))) {
-                // Allow full screen keyguard presentation dialogs to be seen.
+                // Allow full screen keyguard presentation dialogs to be seen, or simply ignore the
+                // keyguard if this display is always unlocked.
                 displayHasContent = true;
             }
             if ((privateflags & PRIVATE_FLAG_SUSTAINED_PERFORMANCE_MODE) != 0) {
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index 39772dda..9c23beb 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -3473,6 +3473,11 @@
                         top.mLetterboxUiController.getLetterboxPositionForVerticalReachability();
             }
         }
+        // User Aspect Ratio Settings is enabled if the app is not in SCM
+        info.topActivityEligibleForUserAspectRatioButton =
+                mWmService.mLetterboxConfiguration.isUserAppAspectRatioSettingsEnabled()
+                        && top != null && !info.topActivityInSizeCompat;
+        info.topActivityBoundsLetterboxed = top != null && top.areBoundsLetterboxed();
     }
 
     /**
diff --git a/services/core/jni/com_android_server_input_InputManagerService.cpp b/services/core/jni/com_android_server_input_InputManagerService.cpp
index c065cb5..e76cbe44 100644
--- a/services/core/jni/com_android_server_input_InputManagerService.cpp
+++ b/services/core/jni/com_android_server_input_InputManagerService.cpp
@@ -971,7 +971,7 @@
 void NativeInputManager::notifyDeviceInteraction(int32_t deviceId, nsecs_t timestamp,
                                                  const std::set<gui::Uid>& uids) {
     static const bool ENABLE_INPUT_DEVICE_USAGE_METRICS =
-            sysprop::InputProperties::enable_input_device_usage_metrics().value_or(true);
+            sysprop::InputProperties::enable_input_device_usage_metrics().value_or(false);
     if (!ENABLE_INPUT_DEVICE_USAGE_METRICS) return;
 
     mInputManager->getMetricsCollector().notifyDeviceInteraction(deviceId, timestamp, uids);
diff --git a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/DefaultImeVisibilityApplierTest.java b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/DefaultImeVisibilityApplierTest.java
index a6ada4d..869497c 100644
--- a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/DefaultImeVisibilityApplierTest.java
+++ b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/DefaultImeVisibilityApplierTest.java
@@ -20,6 +20,7 @@
 import static android.view.WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE;
 
 import static com.android.internal.inputmethod.SoftInputShowHideReason.HIDE_SOFT_INPUT;
+import static com.android.internal.inputmethod.SoftInputShowHideReason.HIDE_SWITCH_USER;
 import static com.android.internal.inputmethod.SoftInputShowHideReason.SHOW_SOFT_INPUT;
 import static com.android.server.inputmethod.ImeVisibilityStateComputer.STATE_HIDE_IME;
 import static com.android.server.inputmethod.ImeVisibilityStateComputer.STATE_HIDE_IME_EXPLICIT;
@@ -43,6 +44,7 @@
 
 import androidx.test.ext.junit.runners.AndroidJUnit4;
 
+import com.android.dx.mockito.inline.extended.ExtendedMockito;
 import com.android.internal.inputmethod.InputBindResult;
 import com.android.internal.inputmethod.StartInputFlags;
 import com.android.internal.inputmethod.StartInputReason;
@@ -165,6 +167,29 @@
         verify(mMockImeTargetVisibilityPolicy).removeImeScreenshot(eq(Display.DEFAULT_DISPLAY));
     }
 
+    @Test
+    public void testApplyImeVisibility_hideImeWhenUnbinding() {
+        mInputMethodManagerService.setAttachedClientForTesting(null);
+        startInputOrWindowGainedFocus(mWindowToken, SOFT_INPUT_STATE_ALWAYS_VISIBLE);
+        ExtendedMockito.spyOn(mVisibilityApplier);
+
+        synchronized (ImfLock.class) {
+            // Simulate the system hides the IME when switching IME services in different users.
+            // (e.g. unbinding the IME from the current user to the profile user)
+            final int displayIdToShowIme = mInputMethodManagerService.getDisplayIdToShowImeLocked();
+            mInputMethodManagerService.hideCurrentInputLocked(mWindowToken, null, 0, null,
+                    HIDE_SWITCH_USER);
+            mInputMethodManagerService.onUnbindCurrentMethodByReset();
+
+            // Expects applyImeVisibility() -> hideIme() will be called to notify WM for syncing
+            // the IME hidden state.
+            verify(mVisibilityApplier).applyImeVisibility(eq(mWindowToken), any(),
+                    eq(STATE_HIDE_IME));
+            verify(mInputMethodManagerService.mWindowManagerInternal).hideIme(
+                    eq(mWindowToken), eq(displayIdToShowIme), eq(null));
+        }
+    }
+
     private InputBindResult startInputOrWindowGainedFocus(IBinder windowToken, int softInputMode) {
         return mInputMethodManagerService.startInputOrWindowGainedFocus(
                 StartInputReason.WINDOW_FOCUS_GAIN /* startInputReason */,
diff --git a/services/tests/displayservicetests/Android.bp b/services/tests/displayservicetests/Android.bp
index b242ec2..f1ff338 100644
--- a/services/tests/displayservicetests/Android.bp
+++ b/services/tests/displayservicetests/Android.bp
@@ -22,11 +22,20 @@
         "src/**/*.java",
     ],
 
+    libs: [
+        "android.test.mock",
+    ],
+
     static_libs: [
-        "services.core",
-        "androidx.test.runner",
-        "androidx.test.rules",
+        "androidx.test.ext.junit",
+        "display-core-libs",
+        "frameworks-base-testutils",
+        "junit",
+        "junit-params",
+        "platform-compat-test-rules",
         "platform-test-annotations",
+        "services.core",
+        "servicestests-utils",
     ],
 
     defaults: [
@@ -47,3 +56,10 @@
         enabled: false,
     },
 }
+
+java_library {
+    name: "display-core-libs",
+    srcs: [
+        "src/com/android/server/display/TestUtils.java",
+    ],
+}
diff --git a/services/tests/displayservicetests/AndroidManifest.xml b/services/tests/displayservicetests/AndroidManifest.xml
index c2e4174..d2bd10d 100644
--- a/services/tests/displayservicetests/AndroidManifest.xml
+++ b/services/tests/displayservicetests/AndroidManifest.xml
@@ -21,6 +21,16 @@
     Insert permissions here. eg:
     <uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS" />
     -->
+    <uses-permission android:name="android.permission.CONTROL_DISPLAY_BRIGHTNESS" />
+    <uses-permission android:name="android.permission.CONTROL_DISPLAY_COLOR_TRANSFORMS" />
+    <uses-permission android:name="android.permission.DEVICE_POWER" />
+    <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS_FULL" />
+    <uses-permission android:name="android.permission.LOG_COMPAT_CHANGE" />
+    <uses-permission android:name="android.permission.MANAGE_ACTIVITY_TASKS" />
+    <uses-permission android:name="android.permission.PACKAGE_USAGE_STATS" />
+    <uses-permission android:name="android.permission.READ_COMPAT_CHANGE_CONFIG" />
+    <uses-permission android:name="android.permission.READ_DEVICE_CONFIG" />
+    <uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS" />
 
     <application android:debuggable="true"
                  android:testOnly="true">
diff --git a/services/tests/servicestests/src/com/android/server/display/AmbientBrightnessStatsTrackerTest.java b/services/tests/displayservicetests/src/com/android/server/display/AmbientBrightnessStatsTrackerTest.java
similarity index 99%
rename from services/tests/servicestests/src/com/android/server/display/AmbientBrightnessStatsTrackerTest.java
rename to services/tests/displayservicetests/src/com/android/server/display/AmbientBrightnessStatsTrackerTest.java
index 2c4fe53..7333bc7 100644
--- a/services/tests/servicestests/src/com/android/server/display/AmbientBrightnessStatsTrackerTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/AmbientBrightnessStatsTrackerTest.java
@@ -11,7 +11,7 @@
  * 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
+ * limitations under the License.
  */
 
 package com.android.server.display;
diff --git a/services/tests/servicestests/src/com/android/server/display/AutomaticBrightnessControllerTest.java b/services/tests/displayservicetests/src/com/android/server/display/AutomaticBrightnessControllerTest.java
similarity index 100%
rename from services/tests/servicestests/src/com/android/server/display/AutomaticBrightnessControllerTest.java
rename to services/tests/displayservicetests/src/com/android/server/display/AutomaticBrightnessControllerTest.java
diff --git a/services/tests/servicestests/src/com/android/server/display/BrightnessMappingStrategyTest.java b/services/tests/displayservicetests/src/com/android/server/display/BrightnessMappingStrategyTest.java
similarity index 98%
rename from services/tests/servicestests/src/com/android/server/display/BrightnessMappingStrategyTest.java
rename to services/tests/displayservicetests/src/com/android/server/display/BrightnessMappingStrategyTest.java
index 5f81869..ee7826f 100644
--- a/services/tests/servicestests/src/com/android/server/display/BrightnessMappingStrategyTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/BrightnessMappingStrategyTest.java
@@ -11,7 +11,7 @@
  * 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
+ * limitations under the License.
  */
 
 package com.android.server.display;
@@ -206,11 +206,11 @@
         BrightnessMappingStrategy strategy = BrightnessMappingStrategy.create(res, ddc, mMockDwbc);
 
         strategy.setBrightnessConfiguration(null);
-        final int N = DISPLAY_LEVELS_BACKLIGHT.length;
+        final int n = DISPLAY_LEVELS_BACKLIGHT.length;
         final float expectedBrightness =
-                (float) DISPLAY_LEVELS_BACKLIGHT[N - 1] / PowerManager.BRIGHTNESS_ON;
+                (float) DISPLAY_LEVELS_BACKLIGHT[n - 1] / PowerManager.BRIGHTNESS_ON;
         assertEquals(expectedBrightness,
-                strategy.getBrightness(LUX_LEVELS[N - 1]), 0.0001f /*tolerance*/);
+                strategy.getBrightness(LUX_LEVELS[n - 1]), 0.0001f /*tolerance*/);
     }
 
     @Test
@@ -270,10 +270,10 @@
 
         // Check that null returns us to the default configuration.
         strategy.setBrightnessConfiguration(null);
-        final int N = DISPLAY_LEVELS_NITS.length;
-        final float expectedBrightness = DISPLAY_LEVELS_NITS[N - 1] / DISPLAY_RANGE_NITS[1];
+        final int n = DISPLAY_LEVELS_NITS.length;
+        final float expectedBrightness = DISPLAY_LEVELS_NITS[n - 1] / DISPLAY_RANGE_NITS[1];
         assertEquals(expectedBrightness,
-                strategy.getBrightness(LUX_LEVELS[N - 1]), 0.0001f /*tolerance*/);
+                strategy.getBrightness(LUX_LEVELS[n - 1]), 0.0001f /*tolerance*/);
     }
 
     @Test
diff --git a/services/tests/servicestests/src/com/android/server/display/BrightnessThrottlerTest.java b/services/tests/displayservicetests/src/com/android/server/display/BrightnessThrottlerTest.java
similarity index 97%
rename from services/tests/servicestests/src/com/android/server/display/BrightnessThrottlerTest.java
rename to services/tests/displayservicetests/src/com/android/server/display/BrightnessThrottlerTest.java
index 46956d7..8faaf59 100644
--- a/services/tests/servicestests/src/com/android/server/display/BrightnessThrottlerTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/BrightnessThrottlerTest.java
@@ -159,7 +159,7 @@
     @Test
     public void testThermalThrottlingSingleLevel() throws Exception {
         final ThrottlingLevel level = new ThrottlingLevel(PowerManager.THERMAL_STATUS_CRITICAL,
-            0.25f);
+                0.25f);
 
         List<ThrottlingLevel> levels = new ArrayList<>();
         levels.add(level);
@@ -184,7 +184,7 @@
         assertEquals(level.brightness, throttler.getBrightnessCap(), 0f);
         assertTrue(throttler.isThrottled());
         assertEquals(BrightnessInfo.BRIGHTNESS_MAX_REASON_THERMAL,
-            throttler.getBrightnessMaxReason());
+                throttler.getBrightnessMaxReason());
 
         // Set status more than high enough to trigger throttling
         listener.notifyThrottling(getSkinTemp(level.thermalStatus + 1));
@@ -192,7 +192,7 @@
         assertEquals(level.brightness, throttler.getBrightnessCap(), 0f);
         assertTrue(throttler.isThrottled());
         assertEquals(BrightnessInfo.BRIGHTNESS_MAX_REASON_THERMAL,
-           throttler.getBrightnessMaxReason());
+                throttler.getBrightnessMaxReason());
 
         // Return to the lower throttling level
         listener.notifyThrottling(getSkinTemp(level.thermalStatus));
@@ -200,7 +200,7 @@
         assertEquals(level.brightness, throttler.getBrightnessCap(), 0f);
         assertTrue(throttler.isThrottled());
         assertEquals(BrightnessInfo.BRIGHTNESS_MAX_REASON_THERMAL,
-            throttler.getBrightnessMaxReason());
+                throttler.getBrightnessMaxReason());
 
         // Cool down
         listener.notifyThrottling(getSkinTemp(level.thermalStatus - 1));
@@ -208,15 +208,15 @@
         assertEquals(PowerManager.BRIGHTNESS_MAX, throttler.getBrightnessCap(), 0f);
         assertFalse(throttler.isThrottled());
         assertEquals(BrightnessInfo.BRIGHTNESS_MAX_REASON_NONE,
-            throttler.getBrightnessMaxReason());
+                throttler.getBrightnessMaxReason());
     }
 
     @Test
     public void testThermalThrottlingMultiLevel() throws Exception {
         final ThrottlingLevel levelLo = new ThrottlingLevel(PowerManager.THERMAL_STATUS_MODERATE,
-            0.62f);
+                0.62f);
         final ThrottlingLevel levelHi = new ThrottlingLevel(PowerManager.THERMAL_STATUS_CRITICAL,
-            0.25f);
+                0.25f);
 
         List<ThrottlingLevel> levels = new ArrayList<>();
         levels.add(levelLo);
@@ -242,7 +242,7 @@
         assertEquals(levelLo.brightness, throttler.getBrightnessCap(), 0f);
         assertTrue(throttler.isThrottled());
         assertEquals(BrightnessInfo.BRIGHTNESS_MAX_REASON_THERMAL,
-            throttler.getBrightnessMaxReason());
+                throttler.getBrightnessMaxReason());
 
         // Set status to an intermediate throttling level
         listener.notifyThrottling(getSkinTemp(levelLo.thermalStatus + 1));
@@ -250,7 +250,7 @@
         assertEquals(levelLo.brightness, throttler.getBrightnessCap(), 0f);
         assertTrue(throttler.isThrottled());
         assertEquals(BrightnessInfo.BRIGHTNESS_MAX_REASON_THERMAL,
-            throttler.getBrightnessMaxReason());
+                throttler.getBrightnessMaxReason());
 
         // Set status to the highest configured throttling level
         listener.notifyThrottling(getSkinTemp(levelHi.thermalStatus));
@@ -258,7 +258,7 @@
         assertEquals(levelHi.brightness, throttler.getBrightnessCap(), 0f);
         assertTrue(throttler.isThrottled());
         assertEquals(BrightnessInfo.BRIGHTNESS_MAX_REASON_THERMAL,
-            throttler.getBrightnessMaxReason());
+                throttler.getBrightnessMaxReason());
 
         // Set status to exceed the highest configured throttling level
         listener.notifyThrottling(getSkinTemp(levelHi.thermalStatus + 1));
@@ -266,7 +266,7 @@
         assertEquals(levelHi.brightness, throttler.getBrightnessCap(), 0f);
         assertTrue(throttler.isThrottled());
         assertEquals(BrightnessInfo.BRIGHTNESS_MAX_REASON_THERMAL,
-            throttler.getBrightnessMaxReason());
+                throttler.getBrightnessMaxReason());
 
         // Return to an intermediate throttling level
         listener.notifyThrottling(getSkinTemp(levelLo.thermalStatus + 1));
@@ -274,7 +274,7 @@
         assertEquals(levelLo.brightness, throttler.getBrightnessCap(), 0f);
         assertTrue(throttler.isThrottled());
         assertEquals(BrightnessInfo.BRIGHTNESS_MAX_REASON_THERMAL,
-            throttler.getBrightnessMaxReason());
+                throttler.getBrightnessMaxReason());
 
         // Return to the lowest configured throttling level
         listener.notifyThrottling(getSkinTemp(levelLo.thermalStatus));
@@ -282,7 +282,7 @@
         assertEquals(levelLo.brightness, throttler.getBrightnessCap(), 0f);
         assertTrue(throttler.isThrottled());
         assertEquals(BrightnessInfo.BRIGHTNESS_MAX_REASON_THERMAL,
-            throttler.getBrightnessMaxReason());
+                throttler.getBrightnessMaxReason());
 
         // Cool down
         listener.notifyThrottling(getSkinTemp(levelLo.thermalStatus - 1));
diff --git a/services/tests/servicestests/src/com/android/server/display/BrightnessTrackerTest.java b/services/tests/displayservicetests/src/com/android/server/display/BrightnessTrackerTest.java
similarity index 98%
rename from services/tests/servicestests/src/com/android/server/display/BrightnessTrackerTest.java
rename to services/tests/displayservicetests/src/com/android/server/display/BrightnessTrackerTest.java
index 021f2d1..44c7dec 100644
--- a/services/tests/servicestests/src/com/android/server/display/BrightnessTrackerTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/BrightnessTrackerTest.java
@@ -11,7 +11,7 @@
  * 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
+ * limitations under the License.
  */
 
 package com.android.server.display;
@@ -395,8 +395,8 @@
         final long currentTime = mInjector.currentTimeMillis();
         notifyBrightnessChanged(mTracker, brightness, displayId, new float[] {1000.0f},
                 new long[] {TimeUnit.NANOSECONDS.toMillis(mInjector.elapsedRealtimeNanos())});
-        List<BrightnessChangeEvent> eventsNoPackage
-                = mTracker.getEvents(0, false).getList();
+        List<BrightnessChangeEvent> eventsNoPackage =
+                mTracker.getEvents(0, false).getList();
         List<BrightnessChangeEvent> events = mTracker.getEvents(0, true).getList();
         mTracker.stop();
 
@@ -1037,9 +1037,9 @@
         }
 
         void setBrightnessMode(boolean isBrightnessModeAutomatic) {
-          mIsBrightnessModeAutomatic = isBrightnessModeAutomatic;
-          mContentObserver.dispatchChange(false, null);
-          waitForHandler();
+            mIsBrightnessModeAutomatic = isBrightnessModeAutomatic;
+            mContentObserver.dispatchChange(false, null);
+            waitForHandler();
         }
 
         void sendScreenChange(boolean screenOn) {
@@ -1184,8 +1184,8 @@
 
         @Override
         public int getNightDisplayColorTemperature(Context context) {
-          return mSecureIntSettings.getOrDefault(Settings.Secure.NIGHT_DISPLAY_COLOR_TEMPERATURE,
-                  mDefaultNightModeColorTemperature);
+            return mSecureIntSettings.getOrDefault(Settings.Secure.NIGHT_DISPLAY_COLOR_TEMPERATURE,
+                    mDefaultNightModeColorTemperature);
         }
 
         @Override
diff --git a/services/tests/servicestests/src/com/android/server/display/ColorFadeTest.java b/services/tests/displayservicetests/src/com/android/server/display/ColorFadeTest.java
similarity index 100%
rename from services/tests/servicestests/src/com/android/server/display/ColorFadeTest.java
rename to services/tests/displayservicetests/src/com/android/server/display/ColorFadeTest.java
diff --git a/services/tests/servicestests/src/com/android/server/display/DeviceStateToLayoutMapTest.java b/services/tests/displayservicetests/src/com/android/server/display/DeviceStateToLayoutMapTest.java
similarity index 100%
rename from services/tests/servicestests/src/com/android/server/display/DeviceStateToLayoutMapTest.java
rename to services/tests/displayservicetests/src/com/android/server/display/DeviceStateToLayoutMapTest.java
diff --git a/services/tests/servicestests/src/com/android/server/display/DisplayDeviceConfigTest.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayDeviceConfigTest.java
similarity index 100%
rename from services/tests/servicestests/src/com/android/server/display/DisplayDeviceConfigTest.java
rename to services/tests/displayservicetests/src/com/android/server/display/DisplayDeviceConfigTest.java
diff --git a/services/tests/servicestests/src/com/android/server/display/DisplayManagerServiceTest.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java
similarity index 99%
rename from services/tests/servicestests/src/com/android/server/display/DisplayManagerServiceTest.java
rename to services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java
index 5db9d1f..d16c9c5 100644
--- a/services/tests/servicestests/src/com/android/server/display/DisplayManagerServiceTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java
@@ -142,7 +142,7 @@
     private static final float FLOAT_TOLERANCE = 0.01f;
 
     private static final String VIRTUAL_DISPLAY_NAME = "Test Virtual Display";
-    private static final String PACKAGE_NAME = "com.android.frameworks.servicestests";
+    private static final String PACKAGE_NAME = "com.android.frameworks.displayservicetests";
     private static final long STANDARD_DISPLAY_EVENTS = DisplayManager.EVENT_FLAG_DISPLAY_ADDED
                     | DisplayManager.EVENT_FLAG_DISPLAY_CHANGED
                     | DisplayManager.EVENT_FLAG_DISPLAY_REMOVED;
@@ -238,7 +238,7 @@
         boolean getHdrOutputConversionSupport() {
             return true;
         }
-   }
+    }
 
     private final DisplayManagerService.Injector mBasicInjector = new BasicInjector();
 
diff --git a/services/tests/servicestests/src/com/android/server/display/HbmEventTest.java b/services/tests/displayservicetests/src/com/android/server/display/HbmEventTest.java
similarity index 100%
rename from services/tests/servicestests/src/com/android/server/display/HbmEventTest.java
rename to services/tests/displayservicetests/src/com/android/server/display/HbmEventTest.java
diff --git a/services/tests/servicestests/src/com/android/server/display/HighBrightnessModeControllerTest.java b/services/tests/displayservicetests/src/com/android/server/display/HighBrightnessModeControllerTest.java
similarity index 89%
rename from services/tests/servicestests/src/com/android/server/display/HighBrightnessModeControllerTest.java
rename to services/tests/displayservicetests/src/com/android/server/display/HighBrightnessModeControllerTest.java
index e2a66f0..76e6ec7 100644
--- a/services/tests/servicestests/src/com/android/server/display/HighBrightnessModeControllerTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/HighBrightnessModeControllerTest.java
@@ -483,8 +483,10 @@
 
         // Verify Stats HBM_ON_HDR
         verify(mInjectorMock).reportHbmStateChange(eq(displayStatsId),
-            eq(FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__STATE__HBM_ON_HDR),
-            eq(FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__REASON__HBM_TRANSITION_REASON_UNKNOWN));
+                eq(FrameworkStatsLog
+                        .DISPLAY_HBM_STATE_CHANGED__STATE__HBM_ON_HDR),
+                eq(FrameworkStatsLog
+                        .DISPLAY_HBM_STATE_CHANGED__REASON__HBM_TRANSITION_REASON_UNKNOWN));
 
         hbmc.getHdrListener().onHdrInfoChanged(null /*displayToken*/, 0 /*numberOfHdrLayers*/,
                 0, 0, 0 /*flags*/, 1.0f /*maxDesiredHdrSdrRatio*/);
@@ -492,8 +494,10 @@
 
         // Verify Stats HBM_OFF
         verify(mInjectorMock).reportHbmStateChange(eq(displayStatsId),
-            eq(FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__STATE__HBM_OFF),
-            eq(FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__REASON__HBM_TRANSITION_REASON_UNKNOWN));
+                eq(FrameworkStatsLog
+                        .DISPLAY_HBM_STATE_CHANGED__STATE__HBM_OFF),
+                eq(FrameworkStatsLog
+                        .DISPLAY_HBM_STATE_CHANGED__REASON__HBM_TRANSITION_REASON_UNKNOWN));
 
         hbmc.setAutoBrightnessEnabled(AUTO_BRIGHTNESS_ENABLED);
         hbmc.onAmbientLuxChange(MINIMUM_LUX + 1);
@@ -501,16 +505,20 @@
 
         // Verify Stats HBM_ON_SUNLIGHT
         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));
+                eq(FrameworkStatsLog
+                        .DISPLAY_HBM_STATE_CHANGED__STATE__HBM_ON_SUNLIGHT),
+                eq(FrameworkStatsLog
+                        .DISPLAY_HBM_STATE_CHANGED__REASON__HBM_TRANSITION_REASON_UNKNOWN));
 
         hbmc.onAmbientLuxChange(1);
         advanceTime(TIME_ALLOWED_IN_WINDOW_MILLIS / 2 + 1);
 
         // Verify Stats HBM_OFF
         verify(mInjectorMock).reportHbmStateChange(eq(displayStatsId),
-            eq(FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__STATE__HBM_OFF),
-            eq(FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__REASON__HBM_SV_OFF_LUX_DROP));
+                eq(FrameworkStatsLog
+                        .DISPLAY_HBM_STATE_CHANGED__STATE__HBM_OFF),
+                eq(FrameworkStatsLog
+                        .DISPLAY_HBM_STATE_CHANGED__REASON__HBM_SV_OFF_LUX_DROP));
     }
 
     @Test
@@ -527,8 +535,8 @@
 
         // Verify Stats HBM_ON_HDR not report
         verify(mInjectorMock, never()).reportHbmStateChange(eq(displayStatsId),
-            eq(FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__STATE__HBM_ON_HDR),
-            anyInt());
+                eq(FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__STATE__HBM_ON_HDR),
+                anyInt());
     }
 
     @Test
@@ -545,8 +553,8 @@
 
         // Verify Stats HBM_ON_SUNLIGHT not report
         verify(mInjectorMock, never()).reportHbmStateChange(eq(displayStatsId),
-            eq(FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__STATE__HBM_ON_SUNLIGHT),
-            anyInt());
+                eq(FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__STATE__HBM_ON_SUNLIGHT),
+                anyInt());
     }
 
     // Test reporting of thermal throttling when triggered externally through
@@ -565,8 +573,10 @@
                 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));
+                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,
@@ -578,8 +588,8 @@
         // the HBM transition point.
         assertEquals(HIGH_BRIGHTNESS_MODE_SUNLIGHT, 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));
+                eq(FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__STATE__HBM_OFF),
+                eq(FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__REASON__HBM_SV_OFF_THERMAL_LIMIT));
     }
 
     @Test
@@ -592,15 +602,17 @@
         hbmcOnBrightnessChanged(hbmc, TRANSITION_POINT + 0.01f);
         advanceTime(0);
         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));
+                eq(FrameworkStatsLog
+                        .DISPLAY_HBM_STATE_CHANGED__STATE__HBM_ON_SUNLIGHT),
+                eq(FrameworkStatsLog
+                        .DISPLAY_HBM_STATE_CHANGED__REASON__HBM_TRANSITION_REASON_UNKNOWN));
 
         // Use up all the time in the window.
         advanceTime(TIME_WINDOW_MILLIS + 1);
 
         verify(mInjectorMock).reportHbmStateChange(eq(displayStatsId),
-            eq(FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__STATE__HBM_OFF),
-            eq(FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__REASON__HBM_SV_OFF_TIME_LIMIT));
+                eq(FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__STATE__HBM_OFF),
+                eq(FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__REASON__HBM_SV_OFF_TIME_LIMIT));
     }
 
     @Test
@@ -613,13 +625,17 @@
         hbmcOnBrightnessChanged(hbmc, TRANSITION_POINT + 0.01f);
         advanceTime(0);
         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));
+                eq(FrameworkStatsLog
+                        .DISPLAY_HBM_STATE_CHANGED__STATE__HBM_ON_SUNLIGHT),
+                eq(FrameworkStatsLog
+                        .DISPLAY_HBM_STATE_CHANGED__REASON__HBM_TRANSITION_REASON_UNKNOWN));
 
         hbmc.setAutoBrightnessEnabled(AUTO_BRIGHTNESS_OFF_DUE_TO_DISPLAY_STATE);
         verify(mInjectorMock).reportHbmStateChange(eq(displayStatsId),
-            eq(FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__STATE__HBM_OFF),
-            eq(FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__REASON__HBM_SV_OFF_DISPLAY_OFF));
+                eq(FrameworkStatsLog
+                        .DISPLAY_HBM_STATE_CHANGED__STATE__HBM_OFF),
+                eq(FrameworkStatsLog
+                        .DISPLAY_HBM_STATE_CHANGED__REASON__HBM_SV_OFF_DISPLAY_OFF));
     }
 
     @Test
@@ -632,16 +648,18 @@
         hbmcOnBrightnessChanged(hbmc, TRANSITION_POINT + 0.01f);
         advanceTime(0);
         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));
+                eq(FrameworkStatsLog
+                        .DISPLAY_HBM_STATE_CHANGED__STATE__HBM_ON_SUNLIGHT),
+                eq(FrameworkStatsLog
+                        .DISPLAY_HBM_STATE_CHANGED__REASON__HBM_TRANSITION_REASON_UNKNOWN));
 
         hbmc.getHdrListener().onHdrInfoChanged(null /*displayToken*/, 1 /*numberOfHdrLayers*/,
                 DISPLAY_WIDTH, DISPLAY_HEIGHT, 0 /*flags*/, 1.0f /*maxDesiredHdrSdrRatio*/);
         advanceTime(0);
 
         verify(mInjectorMock).reportHbmStateChange(eq(displayStatsId),
-            eq(FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__STATE__HBM_ON_HDR),
-            eq(FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__REASON__HBM_SV_OFF_HDR_PLAYING));
+                eq(FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__STATE__HBM_ON_HDR),
+                eq(FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__REASON__HBM_SV_OFF_HDR_PLAYING));
     }
 
     @Test
@@ -657,15 +675,17 @@
         assertEquals(HIGH_BRIGHTNESS_MODE_SUNLIGHT, hbmc.getHighBrightnessMode());
         // verify HBM_ON_SUNLIGHT
         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));
+                eq(FrameworkStatsLog
+                        .DISPLAY_HBM_STATE_CHANGED__STATE__HBM_ON_SUNLIGHT),
+                eq(FrameworkStatsLog
+                        .DISPLAY_HBM_STATE_CHANGED__REASON__HBM_TRANSITION_REASON_UNKNOWN));
 
         hbmcOnBrightnessChanged(hbmc, DEFAULT_MIN);
         // verify HBM_SV_OFF due to LOW_REQUESTED_BRIGHTNESS
         verify(mInjectorMock).reportHbmStateChange(eq(displayStatsId),
-            eq(FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__STATE__HBM_OFF),
-            eq(FrameworkStatsLog
-                      .DISPLAY_HBM_STATE_CHANGED__REASON__HBM_SV_OFF_LOW_REQUESTED_BRIGHTNESS));
+                eq(FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__STATE__HBM_OFF),
+                eq(FrameworkStatsLog
+                        .DISPLAY_HBM_STATE_CHANGED__REASON__HBM_SV_OFF_LOW_REQUESTED_BRIGHTNESS));
     }
 
     private void assertState(HighBrightnessModeController hbmc,
diff --git a/services/tests/servicestests/src/com/android/server/display/HighBrightnessModeMetadataTest.java b/services/tests/displayservicetests/src/com/android/server/display/HighBrightnessModeMetadataTest.java
similarity index 100%
rename from services/tests/servicestests/src/com/android/server/display/HighBrightnessModeMetadataTest.java
rename to services/tests/displayservicetests/src/com/android/server/display/HighBrightnessModeMetadataTest.java
diff --git a/services/tests/servicestests/src/com/android/server/display/LogicalDisplayMapperTest.java b/services/tests/displayservicetests/src/com/android/server/display/LogicalDisplayMapperTest.java
similarity index 100%
rename from services/tests/servicestests/src/com/android/server/display/LogicalDisplayMapperTest.java
rename to services/tests/displayservicetests/src/com/android/server/display/LogicalDisplayMapperTest.java
diff --git a/services/tests/servicestests/src/com/android/server/display/LogicalDisplayTest.java b/services/tests/displayservicetests/src/com/android/server/display/LogicalDisplayTest.java
similarity index 98%
rename from services/tests/servicestests/src/com/android/server/display/LogicalDisplayTest.java
rename to services/tests/displayservicetests/src/com/android/server/display/LogicalDisplayTest.java
index 30ff8ba..2065479 100644
--- a/services/tests/servicestests/src/com/android/server/display/LogicalDisplayTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/LogicalDisplayTest.java
@@ -40,6 +40,7 @@
 import com.android.server.display.layout.Layout;
 
 import org.junit.Before;
+import org.junit.Ignore;
 import org.junit.Test;
 
 import java.io.InputStream;
@@ -121,7 +122,9 @@
         assertEquals(expectedPosition, mLogicalDisplay.getDisplayPosition());
     }
 
+    // TODO: b/288880734 - fix test after display tests migration
     @Test
+    @Ignore
     public void testDisplayInputFlags() {
         SurfaceControl.Transaction t = mock(SurfaceControl.Transaction.class);
         mLogicalDisplay.configureDisplayLocked(t, mDisplayDevice, false);
diff --git a/services/tests/servicestests/src/com/android/server/display/NormalBrightnessModeControllerTest.java b/services/tests/displayservicetests/src/com/android/server/display/NormalBrightnessModeControllerTest.java
similarity index 100%
rename from services/tests/servicestests/src/com/android/server/display/NormalBrightnessModeControllerTest.java
rename to services/tests/displayservicetests/src/com/android/server/display/NormalBrightnessModeControllerTest.java
diff --git a/services/tests/servicestests/src/com/android/server/display/PersistentDataStoreTest.java b/services/tests/displayservicetests/src/com/android/server/display/PersistentDataStoreTest.java
similarity index 99%
rename from services/tests/servicestests/src/com/android/server/display/PersistentDataStoreTest.java
rename to services/tests/displayservicetests/src/com/android/server/display/PersistentDataStoreTest.java
index 642f54c..9f91916 100644
--- a/services/tests/servicestests/src/com/android/server/display/PersistentDataStoreTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/PersistentDataStoreTest.java
@@ -11,7 +11,7 @@
  * 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
+ * limitations under the License.
  */
 
 package com.android.server.display;
diff --git a/services/tests/servicestests/src/com/android/server/display/ScreenOffBrightnessSensorControllerTest.java b/services/tests/displayservicetests/src/com/android/server/display/ScreenOffBrightnessSensorControllerTest.java
similarity index 100%
rename from services/tests/servicestests/src/com/android/server/display/ScreenOffBrightnessSensorControllerTest.java
rename to services/tests/displayservicetests/src/com/android/server/display/ScreenOffBrightnessSensorControllerTest.java
diff --git a/services/tests/servicestests/src/com/android/server/display/utils/SensorUtilsTest.java b/services/tests/displayservicetests/src/com/android/server/display/SensorUtilsTest.java
similarity index 100%
rename from services/tests/servicestests/src/com/android/server/display/utils/SensorUtilsTest.java
rename to services/tests/displayservicetests/src/com/android/server/display/SensorUtilsTest.java
diff --git a/services/tests/displayservicetests/src/com/android/server/display/TestUtils.java b/services/tests/displayservicetests/src/com/android/server/display/TestUtils.java
new file mode 100644
index 0000000..8b45145
--- /dev/null
+++ b/services/tests/displayservicetests/src/com/android/server/display/TestUtils.java
@@ -0,0 +1,94 @@
+/*
+ * Copyright (C) 2019 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 android.hardware.Sensor;
+import android.hardware.SensorEvent;
+import android.hardware.input.InputSensorInfo;
+import android.os.Parcel;
+import android.os.SystemClock;
+import android.view.DisplayAddress;
+
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+
+public final class TestUtils {
+
+    public static SensorEvent createSensorEvent(Sensor sensor, int value) throws Exception {
+        final Constructor<SensorEvent> constructor =
+                SensorEvent.class.getDeclaredConstructor(int.class);
+        constructor.setAccessible(true);
+        final SensorEvent event = constructor.newInstance(1);
+        event.sensor = sensor;
+        event.values[0] = value;
+        event.timestamp = SystemClock.elapsedRealtimeNanos();
+        return event;
+    }
+
+
+    public static void setSensorType(Sensor sensor, int type, String strType) throws Exception {
+        Method setter = Sensor.class.getDeclaredMethod("setType", Integer.TYPE);
+        setter.setAccessible(true);
+        setter.invoke(sensor, type);
+        if (strType != null) {
+            Field f = sensor.getClass().getDeclaredField("mStringType");
+            f.setAccessible(true);
+            f.set(sensor, strType);
+        }
+    }
+
+    public static void setMaximumRange(Sensor sensor, float maximumRange) throws Exception {
+        Method setter = Sensor.class.getDeclaredMethod("setRange", Float.TYPE, Float.TYPE);
+        setter.setAccessible(true);
+        setter.invoke(sensor, maximumRange, 1);
+    }
+
+    public static Sensor createSensor(int type, String strType) throws Exception {
+        Constructor<Sensor> constr = Sensor.class.getDeclaredConstructor();
+        constr.setAccessible(true);
+        Sensor sensor = constr.newInstance();
+        setSensorType(sensor, type, strType);
+        return sensor;
+    }
+
+    public static Sensor createSensor(int type, String strType, float maximumRange)
+            throws Exception {
+        Constructor<Sensor> constr = Sensor.class.getDeclaredConstructor();
+        constr.setAccessible(true);
+        Sensor sensor = constr.newInstance();
+        setSensorType(sensor, type, strType);
+        setMaximumRange(sensor, maximumRange);
+        return sensor;
+    }
+
+    public static Sensor createSensor(String type, String name) {
+        return new Sensor(new InputSensorInfo(
+                name, "vendor", 0, 0, 0, 1f, 1f, 1, 1, 1, 1,
+                type, "", 0, 0, 0));
+    }
+
+    /**
+     * Create a custom {@link DisplayAddress} to ensure we're not relying on any specific
+     * display-address implementation in our code. Intentionally uses default object (reference)
+     * equality rules.
+     */
+    public static class TestDisplayAddress extends DisplayAddress {
+        @Override
+        public void writeToParcel(Parcel out, int flags) { }
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/display/brightness/BrightnessEventTest.java b/services/tests/displayservicetests/src/com/android/server/display/brightness/BrightnessEventTest.java
similarity index 100%
rename from services/tests/servicestests/src/com/android/server/display/brightness/BrightnessEventTest.java
rename to services/tests/displayservicetests/src/com/android/server/display/brightness/BrightnessEventTest.java
diff --git a/services/tests/servicestests/src/com/android/server/display/brightness/BrightnessReasonTest.java b/services/tests/displayservicetests/src/com/android/server/display/brightness/BrightnessReasonTest.java
similarity index 100%
rename from services/tests/servicestests/src/com/android/server/display/brightness/BrightnessReasonTest.java
rename to services/tests/displayservicetests/src/com/android/server/display/brightness/BrightnessReasonTest.java
diff --git a/services/tests/servicestests/src/com/android/server/display/brightness/DisplayBrightnessControllerTest.java b/services/tests/displayservicetests/src/com/android/server/display/brightness/DisplayBrightnessControllerTest.java
similarity index 100%
rename from services/tests/servicestests/src/com/android/server/display/brightness/DisplayBrightnessControllerTest.java
rename to services/tests/displayservicetests/src/com/android/server/display/brightness/DisplayBrightnessControllerTest.java
diff --git a/services/tests/servicestests/src/com/android/server/display/brightness/DisplayBrightnessStrategySelectorTest.java b/services/tests/displayservicetests/src/com/android/server/display/brightness/DisplayBrightnessStrategySelectorTest.java
similarity index 100%
rename from services/tests/servicestests/src/com/android/server/display/brightness/DisplayBrightnessStrategySelectorTest.java
rename to services/tests/displayservicetests/src/com/android/server/display/brightness/DisplayBrightnessStrategySelectorTest.java
diff --git a/services/tests/servicestests/src/com/android/server/display/brightness/strategy/AutomaticBrightnessStrategyTest.java b/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/AutomaticBrightnessStrategyTest.java
similarity index 100%
rename from services/tests/servicestests/src/com/android/server/display/brightness/strategy/AutomaticBrightnessStrategyTest.java
rename to services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/AutomaticBrightnessStrategyTest.java
diff --git a/services/tests/servicestests/src/com/android/server/display/brightness/strategy/BoostBrightnessStrategyTest.java b/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/BoostBrightnessStrategyTest.java
similarity index 100%
rename from services/tests/servicestests/src/com/android/server/display/brightness/strategy/BoostBrightnessStrategyTest.java
rename to services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/BoostBrightnessStrategyTest.java
diff --git a/services/tests/servicestests/src/com/android/server/display/brightness/strategy/DozeBrightnessStrategyTest.java b/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/DozeBrightnessStrategyTest.java
similarity index 100%
rename from services/tests/servicestests/src/com/android/server/display/brightness/strategy/DozeBrightnessStrategyTest.java
rename to services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/DozeBrightnessStrategyTest.java
diff --git a/services/tests/servicestests/src/com/android/server/display/brightness/strategy/FollowerBrightnessStrategyTest.java b/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/FollowerBrightnessStrategyTest.java
similarity index 100%
rename from services/tests/servicestests/src/com/android/server/display/brightness/strategy/FollowerBrightnessStrategyTest.java
rename to services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/FollowerBrightnessStrategyTest.java
diff --git a/services/tests/servicestests/src/com/android/server/display/brightness/strategy/OverrideBrightnessStrategyTest.java b/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/OverrideBrightnessStrategyTest.java
similarity index 100%
rename from services/tests/servicestests/src/com/android/server/display/brightness/strategy/OverrideBrightnessStrategyTest.java
rename to services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/OverrideBrightnessStrategyTest.java
diff --git a/services/tests/servicestests/src/com/android/server/display/brightness/strategy/ScreenOffBrightnessStrategyTest.java b/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/ScreenOffBrightnessStrategyTest.java
similarity index 100%
rename from services/tests/servicestests/src/com/android/server/display/brightness/strategy/ScreenOffBrightnessStrategyTest.java
rename to services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/ScreenOffBrightnessStrategyTest.java
diff --git a/services/tests/servicestests/src/com/android/server/display/brightness/strategy/TemporaryBrightnessStrategyTest.java b/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/TemporaryBrightnessStrategyTest.java
similarity index 100%
rename from services/tests/servicestests/src/com/android/server/display/brightness/strategy/TemporaryBrightnessStrategyTest.java
rename to services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/TemporaryBrightnessStrategyTest.java
diff --git a/services/tests/servicestests/src/com/android/server/display/color/AppSaturationControllerTest.java b/services/tests/displayservicetests/src/com/android/server/display/color/AppSaturationControllerTest.java
similarity index 100%
rename from services/tests/servicestests/src/com/android/server/display/color/AppSaturationControllerTest.java
rename to services/tests/displayservicetests/src/com/android/server/display/color/AppSaturationControllerTest.java
diff --git a/services/tests/servicestests/src/com/android/server/display/color/CctEvaluatorTest.java b/services/tests/displayservicetests/src/com/android/server/display/color/CctEvaluatorTest.java
similarity index 100%
rename from services/tests/servicestests/src/com/android/server/display/color/CctEvaluatorTest.java
rename to services/tests/displayservicetests/src/com/android/server/display/color/CctEvaluatorTest.java
diff --git a/services/tests/servicestests/src/com/android/server/display/color/ColorDisplayServiceTest.java b/services/tests/displayservicetests/src/com/android/server/display/color/ColorDisplayServiceTest.java
similarity index 98%
rename from services/tests/servicestests/src/com/android/server/display/color/ColorDisplayServiceTest.java
rename to services/tests/displayservicetests/src/com/android/server/display/color/ColorDisplayServiceTest.java
index 618ab1b..c7c09b5 100644
--- a/services/tests/servicestests/src/com/android/server/display/color/ColorDisplayServiceTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/color/ColorDisplayServiceTest.java
@@ -1093,15 +1093,15 @@
     @Test
     public void compositionColorSpaces_invalidResources() {
         when(mResourcesSpy.getIntArray(R.array.config_displayCompositionColorModes))
-            .thenReturn(new int[] {
-               ColorDisplayManager.COLOR_MODE_NATURAL,
-               // Missing second color mode
-            });
+                .thenReturn(new int[] {
+                        ColorDisplayManager.COLOR_MODE_NATURAL,
+                        // Missing second color mode
+                });
         when(mResourcesSpy.getIntArray(R.array.config_displayCompositionColorSpaces))
-            .thenReturn(new int[] {
-               Display.COLOR_MODE_SRGB,
-               Display.COLOR_MODE_DISPLAY_P3
-            });
+                .thenReturn(new int[] {
+                        Display.COLOR_MODE_SRGB,
+                        Display.COLOR_MODE_DISPLAY_P3
+                });
         setColorMode(ColorDisplayManager.COLOR_MODE_NATURAL);
         startService();
         verify(mDisplayTransformManager).setColorMode(
@@ -1111,13 +1111,13 @@
     @Test
     public void compositionColorSpaces_validResources_validColorMode() {
         when(mResourcesSpy.getIntArray(R.array.config_displayCompositionColorModes))
-            .thenReturn(new int[] {
-               ColorDisplayManager.COLOR_MODE_NATURAL
-            });
+                .thenReturn(new int[] {
+                        ColorDisplayManager.COLOR_MODE_NATURAL
+                });
         when(mResourcesSpy.getIntArray(R.array.config_displayCompositionColorSpaces))
-            .thenReturn(new int[] {
-               Display.COLOR_MODE_SRGB,
-            });
+                .thenReturn(new int[] {
+                        Display.COLOR_MODE_SRGB,
+                });
         setColorMode(ColorDisplayManager.COLOR_MODE_NATURAL);
         startService();
         verify(mDisplayTransformManager).setColorMode(
@@ -1127,13 +1127,13 @@
     @Test
     public void compositionColorSpaces_validResources_invalidColorMode() {
         when(mResourcesSpy.getIntArray(R.array.config_displayCompositionColorModes))
-            .thenReturn(new int[] {
-               ColorDisplayManager.COLOR_MODE_NATURAL
-            });
+                .thenReturn(new int[] {
+                        ColorDisplayManager.COLOR_MODE_NATURAL
+                });
         when(mResourcesSpy.getIntArray(R.array.config_displayCompositionColorSpaces))
-            .thenReturn(new int[] {
-               Display.COLOR_MODE_SRGB,
-            });
+                .thenReturn(new int[] {
+                        Display.COLOR_MODE_SRGB,
+                });
         setColorMode(ColorDisplayManager.COLOR_MODE_BOOSTED);
         startService();
         verify(mDisplayTransformManager).setColorMode(
@@ -1143,7 +1143,7 @@
     @Test
     public void getColorMode_noAvailableModes_returnsNotSet() {
         when(mResourcesSpy.getIntArray(R.array.config_availableColorModes))
-                .thenReturn(new int[] {});
+                    .thenReturn(new int[] {});
         startService();
         verify(mDisplayTransformManager, never()).setColorMode(anyInt(), any(), anyInt());
         assertThat(mBinderService.getColorMode()).isEqualTo(-1);
diff --git a/services/tests/servicestests/src/com/android/server/display/color/DisplayWhiteBalanceTintControllerTest.java b/services/tests/displayservicetests/src/com/android/server/display/color/DisplayWhiteBalanceTintControllerTest.java
similarity index 100%
rename from services/tests/servicestests/src/com/android/server/display/color/DisplayWhiteBalanceTintControllerTest.java
rename to services/tests/displayservicetests/src/com/android/server/display/color/DisplayWhiteBalanceTintControllerTest.java
diff --git a/services/tests/servicestests/src/com/android/server/display/color/GlobalSaturationTintControllerTest.java b/services/tests/displayservicetests/src/com/android/server/display/color/GlobalSaturationTintControllerTest.java
similarity index 100%
rename from services/tests/servicestests/src/com/android/server/display/color/GlobalSaturationTintControllerTest.java
rename to services/tests/displayservicetests/src/com/android/server/display/color/GlobalSaturationTintControllerTest.java
diff --git a/services/tests/servicestests/src/com/android/server/display/color/ReduceBrightColorsTintControllerTest.java b/services/tests/displayservicetests/src/com/android/server/display/color/ReduceBrightColorsTintControllerTest.java
similarity index 100%
rename from services/tests/servicestests/src/com/android/server/display/color/ReduceBrightColorsTintControllerTest.java
rename to services/tests/displayservicetests/src/com/android/server/display/color/ReduceBrightColorsTintControllerTest.java
diff --git a/services/tests/servicestests/src/com/android/server/display/mode/DisplayModeDirectorTest.java b/services/tests/displayservicetests/src/com/android/server/display/mode/DisplayModeDirectorTest.java
similarity index 100%
rename from services/tests/servicestests/src/com/android/server/display/mode/DisplayModeDirectorTest.java
rename to services/tests/displayservicetests/src/com/android/server/display/mode/DisplayModeDirectorTest.java
diff --git a/services/tests/servicestests/src/com/android/server/display/mode/SkinThermalStatusObserverTest.java b/services/tests/displayservicetests/src/com/android/server/display/mode/SkinThermalStatusObserverTest.java
similarity index 100%
rename from services/tests/servicestests/src/com/android/server/display/mode/SkinThermalStatusObserverTest.java
rename to services/tests/displayservicetests/src/com/android/server/display/mode/SkinThermalStatusObserverTest.java
diff --git a/services/tests/servicestests/src/com/android/server/display/mode/VotesStorageTest.java b/services/tests/displayservicetests/src/com/android/server/display/mode/VotesStorageTest.java
similarity index 100%
rename from services/tests/servicestests/src/com/android/server/display/mode/VotesStorageTest.java
rename to services/tests/displayservicetests/src/com/android/server/display/mode/VotesStorageTest.java
diff --git a/services/tests/servicestests/src/com/android/server/display/utils/AmbientFilterTest.java b/services/tests/displayservicetests/src/com/android/server/display/utils/AmbientFilterTest.java
similarity index 100%
rename from services/tests/servicestests/src/com/android/server/display/utils/AmbientFilterTest.java
rename to services/tests/displayservicetests/src/com/android/server/display/utils/AmbientFilterTest.java
diff --git a/services/tests/servicestests/src/com/android/server/display/whitebalance/AmbientFilterStubber.java b/services/tests/displayservicetests/src/com/android/server/display/whitebalance/AmbientFilterStubber.java
similarity index 100%
rename from services/tests/servicestests/src/com/android/server/display/whitebalance/AmbientFilterStubber.java
rename to services/tests/displayservicetests/src/com/android/server/display/whitebalance/AmbientFilterStubber.java
diff --git a/services/tests/servicestests/src/com/android/server/display/whitebalance/AmbientLuxTest.java b/services/tests/displayservicetests/src/com/android/server/display/whitebalance/AmbientLuxTest.java
similarity index 94%
rename from services/tests/servicestests/src/com/android/server/display/whitebalance/AmbientLuxTest.java
rename to services/tests/displayservicetests/src/com/android/server/display/whitebalance/AmbientLuxTest.java
index ac97911..f975b6f 100644
--- a/services/tests/servicestests/src/com/android/server/display/whitebalance/AmbientLuxTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/whitebalance/AmbientLuxTest.java
@@ -268,9 +268,9 @@
         controller.mBrightnessFilter = spy(new AmbientFilterStubber());
 
         for (float luxOverride = 0.1f; luxOverride <= 10000; luxOverride *= 10) {
-        setEstimatedBrightnessAndUpdate(controller, luxOverride);
-        assertEquals(controller.mPendingAmbientColorTemperature,
-                ambientColorTemperature, 0.001);
+            setEstimatedBrightnessAndUpdate(controller, luxOverride);
+            assertEquals(controller.mPendingAmbientColorTemperature,
+                    ambientColorTemperature, 0.001);
         }
     }
 
@@ -286,9 +286,9 @@
         controller.mBrightnessFilter = spy(new AmbientFilterStubber());
 
         for (float luxOverride = 0.1f; luxOverride <= 10000; luxOverride *= 10) {
-        setEstimatedBrightnessAndUpdate(controller, luxOverride);
-        assertEquals(controller.mPendingAmbientColorTemperature,
-                ambientColorTemperature, 0.001);
+            setEstimatedBrightnessAndUpdate(controller, luxOverride);
+            assertEquals(controller.mPendingAmbientColorTemperature,
+                    ambientColorTemperature, 0.001);
         }
     }
 
@@ -366,22 +366,22 @@
 
     @Test
     public void testSpline_InvalidCombinations() throws Exception {
-            setBrightnesses(100.0f, 200.0f);
-            setBiases(0.0f, 1.0f);
-            setHighLightBrightnesses(150.0f, 250.0f);
-            setHighLightBiases(0.0f, 1.0f);
+        setBrightnesses(100.0f, 200.0f);
+        setBiases(0.0f, 1.0f);
+        setHighLightBrightnesses(150.0f, 250.0f);
+        setHighLightBiases(0.0f, 1.0f);
 
-            DisplayWhiteBalanceController controller =
-                    DisplayWhiteBalanceFactory.create(mHandler, mSensorManagerMock, mResourcesSpy);
-            final float ambientColorTemperature = 8000.0f;
-            setEstimatedColorTemperature(controller, ambientColorTemperature);
-            controller.mBrightnessFilter = spy(new AmbientFilterStubber());
+        DisplayWhiteBalanceController controller =
+                DisplayWhiteBalanceFactory.create(mHandler, mSensorManagerMock, mResourcesSpy);
+        final float ambientColorTemperature = 8000.0f;
+        setEstimatedColorTemperature(controller, ambientColorTemperature);
+        controller.mBrightnessFilter = spy(new AmbientFilterStubber());
 
-            for (float luxOverride = 0.1f; luxOverride <= 10000; luxOverride *= 10) {
-                setEstimatedBrightnessAndUpdate(controller, luxOverride);
-                assertEquals(controller.mPendingAmbientColorTemperature,
-                        ambientColorTemperature, 0.001);
-            }
+        for (float luxOverride = 0.1f; luxOverride <= 10000; luxOverride *= 10) {
+            setEstimatedBrightnessAndUpdate(controller, luxOverride);
+            assertEquals(controller.mPendingAmbientColorTemperature,
+                    ambientColorTemperature, 0.001);
+        }
     }
 
     @Test
@@ -486,7 +486,7 @@
     private void mockResourcesFloat(int id, float floatValue) {
         doAnswer(new Answer<Void>() {
             public Void answer(InvocationOnMock invocation) {
-                TypedValue value = (TypedValue)invocation.getArgument(1);
+                TypedValue value = (TypedValue) invocation.getArgument(1);
                 value.type = TypedValue.TYPE_FLOAT;
                 value.data = Float.floatToIntBits(floatValue);
                 return null;
diff --git a/services/tests/servicestests/src/com/android/server/display/whitebalance/AmbientSensorTest.java b/services/tests/displayservicetests/src/com/android/server/display/whitebalance/AmbientSensorTest.java
similarity index 100%
rename from services/tests/servicestests/src/com/android/server/display/whitebalance/AmbientSensorTest.java
rename to services/tests/displayservicetests/src/com/android/server/display/whitebalance/AmbientSensorTest.java
diff --git a/services/tests/mockingservicestests/src/android/service/games/GameSessionTrampolineActivityTest.java b/services/tests/mockingservicestests/src/android/service/games/GameSessionTrampolineActivityTest.java
index 353341e..82e02ca 100644
--- a/services/tests/mockingservicestests/src/android/service/games/GameSessionTrampolineActivityTest.java
+++ b/services/tests/mockingservicestests/src/android/service/games/GameSessionTrampolineActivityTest.java
@@ -47,11 +47,14 @@
 
 import com.android.internal.infra.AndroidFuture;
 
+import com.google.common.util.concurrent.Uninterruptibles;
+
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
+import java.time.Duration;
 import java.util.concurrent.TimeUnit;
 
 /**
@@ -62,6 +65,8 @@
 @Presubmit
 public class GameSessionTrampolineActivityTest {
 
+    private static final Duration TEST_ACTIVITY_OPEN_DURATION = Duration.ofSeconds(5);
+
     @Before
     public void setUp() {
         setAlwaysFinishActivities(false);
@@ -145,10 +150,15 @@
             startTestActivityViaGameSessionTrampolineActivity() {
         Intent testActivityIntent = new Intent();
         testActivityIntent.setClass(getInstrumentation().getTargetContext(), TestActivity.class);
+        sleep(TEST_ACTIVITY_OPEN_DURATION);
 
         return startGameSessionTrampolineActivity(testActivityIntent);
     }
 
+    private static void sleep(Duration sleepDuration) {
+        Uninterruptibles.sleepUninterruptibly(sleepDuration.toMillis(), TimeUnit.MILLISECONDS);
+    }
+
     private static AndroidFuture<GameSessionActivityResult> startGameSessionTrampolineActivity(
             Intent targetIntent) {
         AndroidFuture<GameSessionActivityResult> resultFuture = new AndroidFuture<>();
diff --git a/services/tests/mockingservicestests/src/com/android/server/app/GameManagerServiceTests.java b/services/tests/mockingservicestests/src/com/android/server/app/GameManagerServiceTests.java
index 32243f0..212a243 100644
--- a/services/tests/mockingservicestests/src/com/android/server/app/GameManagerServiceTests.java
+++ b/services/tests/mockingservicestests/src/com/android/server/app/GameManagerServiceTests.java
@@ -2221,7 +2221,7 @@
         String[] packages = {mPackageName};
         when(mMockPackageManager.getPackagesForUid(DEFAULT_PACKAGE_UID)).thenReturn(packages);
         gameManagerService.mUidObserver.onUidStateChanged(
-                DEFAULT_PACKAGE_UID, ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE, 0, 0);
+                DEFAULT_PACKAGE_UID, ActivityManager.PROCESS_STATE_TOP, 0, 0);
         verify(mMockPowerManager, times(1)).setPowerMode(Mode.GAME, true);
     }
 
@@ -2238,12 +2238,12 @@
         doAnswer(inv -> powerState.put(inv.getArgument(0), inv.getArgument(1)))
                 .when(mMockPowerManager).setPowerMode(anyInt(), anyBoolean());
         gameManagerService.mUidObserver.onUidStateChanged(
-                DEFAULT_PACKAGE_UID, ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE, 0, 0);
+                DEFAULT_PACKAGE_UID, ActivityManager.PROCESS_STATE_TOP, 0, 0);
         assertTrue(powerState.get(Mode.GAME));
         gameManagerService.mUidObserver.onUidStateChanged(
                 DEFAULT_PACKAGE_UID, ActivityManager.PROCESS_STATE_TRANSIENT_BACKGROUND, 0, 0);
         gameManagerService.mUidObserver.onUidStateChanged(
-                somePackageId, ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE, 0, 0);
+                somePackageId, ActivityManager.PROCESS_STATE_TOP, 0, 0);
         assertTrue(powerState.get(Mode.GAME));
         gameManagerService.mUidObserver.onUidStateChanged(
                 somePackageId, ActivityManager.PROCESS_STATE_TRANSIENT_BACKGROUND, 0, 0);
@@ -2260,13 +2260,13 @@
         int somePackageId = DEFAULT_PACKAGE_UID + 1;
         when(mMockPackageManager.getPackagesForUid(somePackageId)).thenReturn(packages2);
         gameManagerService.mUidObserver.onUidStateChanged(
+                DEFAULT_PACKAGE_UID, ActivityManager.PROCESS_STATE_TOP, 0, 0);
+        gameManagerService.mUidObserver.onUidStateChanged(
+                somePackageId, ActivityManager.PROCESS_STATE_TOP, 0, 0);
+        gameManagerService.mUidObserver.onUidStateChanged(
                 DEFAULT_PACKAGE_UID, ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE, 0, 0);
         gameManagerService.mUidObserver.onUidStateChanged(
                 somePackageId, ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE, 0, 0);
-        gameManagerService.mUidObserver.onUidStateChanged(
-                DEFAULT_PACKAGE_UID, ActivityManager.PROCESS_STATE_TRANSIENT_BACKGROUND, 0, 0);
-        gameManagerService.mUidObserver.onUidStateChanged(
-                somePackageId, ActivityManager.PROCESS_STATE_TRANSIENT_BACKGROUND, 0, 0);
         verify(mMockPowerManager, times(1)).setPowerMode(Mode.GAME, true);
         verify(mMockPowerManager, times(1)).setPowerMode(Mode.GAME, false);
     }
@@ -2277,9 +2277,9 @@
         String[] packages = {mPackageName};
         when(mMockPackageManager.getPackagesForUid(DEFAULT_PACKAGE_UID)).thenReturn(packages);
         gameManagerService.mUidObserver.onUidStateChanged(
-                DEFAULT_PACKAGE_UID, ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE, 0, 0);
+                DEFAULT_PACKAGE_UID, ActivityManager.PROCESS_STATE_TOP, 0, 0);
         gameManagerService.mUidObserver.onUidStateChanged(
-                DEFAULT_PACKAGE_UID, ActivityManager.PROCESS_STATE_TRANSIENT_BACKGROUND, 0, 0);
+                DEFAULT_PACKAGE_UID, ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE, 0, 0);
         verify(mMockPowerManager, times(1)).setPowerMode(Mode.GAME, false);
     }
 
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/SensorOverlaysTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/SensorOverlaysTest.java
index 21c9c75..5012335 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/SensorOverlaysTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/SensorOverlaysTest.java
@@ -61,7 +61,7 @@
 
     @Test
     public void noopWhenBothNull() {
-        final SensorOverlays useless = new SensorOverlays(null, null, null);
+        final SensorOverlays useless = new SensorOverlays(null, null);
         useless.show(SENSOR_ID, 2, null);
         useless.hide(SENSOR_ID);
     }
@@ -69,12 +69,12 @@
     @Test
     public void testProvidesUdfps() {
         final List<IUdfpsOverlayController> udfps = new ArrayList<>();
-        SensorOverlays sensorOverlays = new SensorOverlays(null, mSidefpsController, null);
+        SensorOverlays sensorOverlays = new SensorOverlays(null, mSidefpsController);
 
         sensorOverlays.ifUdfps(udfps::add);
         assertThat(udfps).isEmpty();
 
-        sensorOverlays = new SensorOverlays(mUdfpsOverlayController, mSidefpsController, null);
+        sensorOverlays = new SensorOverlays(mUdfpsOverlayController, mSidefpsController);
         sensorOverlays.ifUdfps(udfps::add);
         assertThat(udfps).containsExactly(mUdfpsOverlayController);
     }
@@ -96,7 +96,7 @@
 
     private void testShow(IUdfpsOverlayController udfps, ISidefpsController sidefps)
             throws Exception {
-        final SensorOverlays sensorOverlays = new SensorOverlays(udfps, sidefps, null);
+        final SensorOverlays sensorOverlays = new SensorOverlays(udfps, sidefps);
         final int reason = BiometricOverlayConstants.REASON_UNKNOWN;
         sensorOverlays.show(SENSOR_ID, reason, mAcquisitionClient);
 
@@ -126,7 +126,7 @@
 
     private void testHide(IUdfpsOverlayController udfps, ISidefpsController sidefps)
             throws Exception {
-        final SensorOverlays sensorOverlays = new SensorOverlays(udfps, sidefps, null);
+        final SensorOverlays sensorOverlays = new SensorOverlays(udfps, sidefps);
         sensorOverlays.hide(SENSOR_ID);
 
         if (udfps != null) {
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClientTest.java
index c383a96..46af905 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClientTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClientTest.java
@@ -436,7 +436,7 @@
                 mBiometricLogger, mBiometricContext,
                 true /* isStrongBiometric */,
                 null /* taskStackListener */, null /* lockoutCache */,
-                mUdfpsOverlayController, mSideFpsController, null, allowBackgroundAuthentication,
+                mUdfpsOverlayController, mSideFpsController, allowBackgroundAuthentication,
                 mSensorProps,
                 new Handler(mLooper.getLooper()), 0 /* biometricStrength */, mClock) {
             @Override
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintDetectClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintDetectClientTest.java
index 6dfdd87..20d5f93 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintDetectClientTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintDetectClientTest.java
@@ -163,6 +163,6 @@
                         .setOpPackageName("a-test")
                         .build(),
                 mBiometricLogger, mBiometricContext,
-                mUdfpsOverlayController, null, true /* isStrongBiometric */);
+                mUdfpsOverlayController, true /* isStrongBiometric */);
     }
 }
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClientTest.java
index 3c89278..ef25380 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClientTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClientTest.java
@@ -296,6 +296,6 @@
         mClientMonitorCallbackConverter, 0 /* userId */,
         HAT, "owner", mBiometricUtils, 8 /* sensorId */,
         mBiometricLogger, mBiometricContext, mSensorProps, mUdfpsOverlayController,
-        mSideFpsController, null, 6 /* maxTemplatesPerUser */, FingerprintManager.ENROLL_ENROLL);
+        mSideFpsController, 6 /* maxTemplatesPerUser */, FingerprintManager.ENROLL_ENROLL);
     }
 }
diff --git a/services/tests/servicestests/src/com/android/server/display/OWNERS b/services/tests/servicestests/src/com/android/server/display/OWNERS
deleted file mode 100644
index 6ce1ee4..0000000
--- a/services/tests/servicestests/src/com/android/server/display/OWNERS
+++ /dev/null
@@ -1 +0,0 @@
-include /services/core/java/com/android/server/display/OWNERS
diff --git a/services/tests/servicestests/src/com/android/server/display/TEST_MAPPING b/services/tests/servicestests/src/com/android/server/display/TEST_MAPPING
deleted file mode 100644
index 92d8abd..0000000
--- a/services/tests/servicestests/src/com/android/server/display/TEST_MAPPING
+++ /dev/null
@@ -1,13 +0,0 @@
-{
-  "presubmit": [
-    {
-      "name": "FrameworksServicesTests",
-      "options": [
-        {"include-filter": "com.android.server.display"},
-        {"exclude-annotation": "android.platform.test.annotations.FlakyTest"},
-        {"exclude-annotation": "androidx.test.filters.FlakyTest"},
-        {"exclude-annotation": "org.junit.Ignore"}
-      ]
-    }
-  ]
-}
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskTests.java
index 28241d3..f332b69 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskTests.java
@@ -563,6 +563,36 @@
         assertEquals(freeformBounds, task.getBounds());
     }
 
+    @Test
+    public void testTopActivityEligibleForUserAspectRatioButton() {
+        DisplayContent display = mAtm.mRootWindowContainer.getDefaultDisplay();
+        final Task rootTask = new TaskBuilder(mSupervisor).setCreateActivity(true)
+                .setWindowingMode(WINDOWING_MODE_FULLSCREEN).setDisplay(display).build();
+        final Task task = rootTask.getBottomMostTask();
+        final ActivityRecord root = task.getTopNonFinishingActivity();
+        spyOn(mWm.mLetterboxConfiguration);
+
+        // When device config flag is disabled the button is not enabled
+        doReturn(false).when(mWm.mLetterboxConfiguration)
+                .isUserAppAspectRatioSettingsEnabled();
+        doReturn(false).when(mWm.mLetterboxConfiguration)
+                .isTranslucentLetterboxingEnabled();
+        assertFalse(task.getTaskInfo().topActivityEligibleForUserAspectRatioButton);
+
+        // The flag is enabled
+        doReturn(true).when(mWm.mLetterboxConfiguration)
+                .isUserAppAspectRatioSettingsEnabled();
+        spyOn(root);
+        doReturn(task).when(root).getOrganizedTask();
+        // When the flag is enabled and the top activity is not in size compat mode.
+        doReturn(false).when(root).inSizeCompatMode();
+        assertTrue(task.getTaskInfo().topActivityEligibleForUserAspectRatioButton);
+
+        // When in size compat mode the button is not enabled
+        doReturn(true).when(root).inSizeCompatMode();
+        assertFalse(task.getTaskInfo().topActivityEligibleForUserAspectRatioButton);
+    }
+
     /**
      * Tests that a task with forced orientation has orientation-consistent bounds within the
      * parent.
diff --git a/services/usb/java/com/android/server/usb/UsbAlsaDevice.java b/services/usb/java/com/android/server/usb/UsbAlsaDevice.java
index 7fe8582..c508fa9 100644
--- a/services/usb/java/com/android/server/usb/UsbAlsaDevice.java
+++ b/services/usb/java/com/android/server/usb/UsbAlsaDevice.java
@@ -59,6 +59,8 @@
     private String mDeviceName = "";
     private String mDeviceDescription = "";
 
+    private boolean mHasJackDetect = true;
+
     public UsbAlsaDevice(IAudioService audioService, int card, int device, String deviceAddress,
             boolean hasOutput, boolean hasInput,
             boolean isInputHeadset, boolean isOutputHeadset, boolean isDock) {
@@ -168,8 +170,14 @@
         if (mJackDetector != null) {
             return;
         }
+        if (!mHasJackDetect) {
+            return;
+        }
         // If no jack detect capabilities exist, mJackDetector will be null.
         mJackDetector = UsbAlsaJackDetector.startJackDetect(this);
+        if (mJackDetector == null) {
+            mHasJackDetect = false;
+        }
     }
 
     /** Stops a jack-detection thread. */
@@ -182,8 +190,8 @@
 
     /** Start using this device as the selected USB Audio Device. */
     public synchronized void start() {
-        startInput();
         startOutput();
+        startInput();
     }
 
     /** Start using this device as the selected USB input device. */
@@ -208,8 +216,8 @@
 
     /** Stop using this device as the selected USB Audio Device. */
     public synchronized void stop() {
-        stopInput();
         stopOutput();
+        stopInput();
     }
 
     /** Stop using this device as the selected USB input device. */
diff --git a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java
index 13945a1..997015f 100644
--- a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java
+++ b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java
@@ -407,7 +407,9 @@
                 var eventLogger = new EventLogger(SESSION_MAX_EVENT_SIZE,
                         "SoundTriggerSessionLogs for package: "
                         + Objects.requireNonNull(originatorIdentity.packageName)
-                        + "#" + sessionId);
+                        + "#" + sessionId
+                        + " - " + originatorIdentity.uid
+                        + "|" + originatorIdentity.pid);
                 return new SoundTriggerSessionStub(client,
                         newSoundTriggerHelper(moduleProperties, eventLogger), eventLogger);
             }
@@ -428,7 +430,9 @@
                 var eventLogger = new EventLogger(SESSION_MAX_EVENT_SIZE,
                         "SoundTriggerSessionLogs for package: "
                         + Objects.requireNonNull(originatorIdentity.packageName) + "#"
-                        + sessionId);
+                        + sessionId
+                        + " - " + originatorIdentity.uid
+                        + "|" + originatorIdentity.pid);
                 return new SoundTriggerSessionStub(client,
                         newSoundTriggerHelper(moduleProperties, eventLogger), eventLogger);
             }
@@ -1801,7 +1805,9 @@
                         ServiceEvent.Type.ATTACH, identity.packageName + "#" + sessionId));
             var eventLogger = new EventLogger(SESSION_MAX_EVENT_SIZE,
                     "LocalSoundTriggerEventLogger for package: " +
-                    identity.packageName + "#" + sessionId);
+                    identity.packageName + "#" + sessionId
+                        + " - " + identity.uid
+                        + "|" + identity.pid);
 
             return new SessionImpl(newSoundTriggerHelper(underlyingModule, eventLogger, isTrusted),
                     client, eventLogger, identity);
diff --git a/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/DefaultHalFactory.java b/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/DefaultHalFactory.java
index 2f2cb59..55cbf29 100644
--- a/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/DefaultHalFactory.java
+++ b/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/DefaultHalFactory.java
@@ -22,7 +22,7 @@
 import android.os.RemoteException;
 import android.os.ServiceManager;
 import android.os.SystemProperties;
-import android.util.Log;
+import android.util.Slog;
 
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -62,7 +62,7 @@
                         android.hardware.soundtrigger3.ISoundTriggerHw.class.getCanonicalName()
                                 + "/default";
                 if (ServiceManager.isDeclared(aidlServiceName)) {
-                    Log.i(TAG, "Connecting to default soundtrigger3.ISoundTriggerHw");
+                    Slog.i(TAG, "Connecting to default soundtrigger3.ISoundTriggerHw");
                     return new SoundTriggerHw3Compat(ServiceManager.waitForService(aidlServiceName),
                             () -> {
                                 // This property needs to be defined in an init.rc script and
@@ -72,7 +72,7 @@
                 }
 
                 // Fallback to soundtrigger-V2.x (HIDL).
-                Log.i(TAG, "Connecting to default soundtrigger-V2.x.ISoundTriggerHw");
+                Slog.i(TAG, "Connecting to default soundtrigger-V2.x.ISoundTriggerHw");
                 ISoundTriggerHw driver = ISoundTriggerHw.getService(true);
                 return SoundTriggerHw2Compat.create(driver, () -> {
                     // This property needs to be defined in an init.rc script and
@@ -81,7 +81,7 @@
                 }, mCaptureStateNotifier);
             } else if (mockHal == USE_MOCK_HAL_V2) {
                 // Use V2 mock.
-                Log.i(TAG, "Connecting to mock soundtrigger-V2.x.ISoundTriggerHw");
+                Slog.i(TAG, "Connecting to mock soundtrigger-V2.x.ISoundTriggerHw");
                 HwBinder.setTrebleTestingOverride(true);
                 try {
                     ISoundTriggerHw driver = ISoundTriggerHw.getService("mock", true);
@@ -89,7 +89,7 @@
                         try {
                             driver.debug(null, new ArrayList<>(Arrays.asList("reboot")));
                         } catch (Exception e) {
-                            Log.e(TAG, "Failed to reboot mock HAL", e);
+                            Slog.e(TAG, "Failed to reboot mock HAL", e);
                         }
                     }, mCaptureStateNotifier);
                 } finally {
@@ -100,14 +100,14 @@
                 final String aidlServiceName =
                         android.hardware.soundtrigger3.ISoundTriggerHw.class.getCanonicalName()
                                 + "/mock";
-                Log.i(TAG, "Connecting to mock soundtrigger3.ISoundTriggerHw");
+                Slog.i(TAG, "Connecting to mock soundtrigger3.ISoundTriggerHw");
                 return new SoundTriggerHw3Compat(ServiceManager.waitForService(aidlServiceName),
                         () -> {
                             try {
                                 ServiceManager.waitForService(aidlServiceName).shellCommand(null,
                                         null, null, new String[]{"reboot"}, null, null);
                             } catch (Exception e) {
-                                Log.e(TAG, "Failed to reboot mock HAL", e);
+                                Slog.e(TAG, "Failed to reboot mock HAL", e);
                             }
                         });
             } else {
diff --git a/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/ExternalCaptureStateTracker.java b/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/ExternalCaptureStateTracker.java
index d195fbe..e3d64d4 100644
--- a/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/ExternalCaptureStateTracker.java
+++ b/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/ExternalCaptureStateTracker.java
@@ -17,7 +17,7 @@
 package com.android.server.soundtrigger_middleware;
 
 import android.annotation.NonNull;
-import android.util.Log;
+import android.util.Slog;
 
 import java.util.LinkedList;
 import java.util.List;
@@ -94,7 +94,7 @@
                 }
             }
         } catch (Exception e) {
-            Log.e(TAG, "Exception caught while setting capture state", e);
+            Slog.e(TAG, "Exception caught while setting capture state", e);
         }
     }
 
@@ -102,7 +102,7 @@
      * Called by native code when the remote service died.
      */
     private void binderDied() {
-        Log.w(TAG, "Audio policy service died");
+        Slog.w(TAG, "Audio policy service died");
         mNeedToConnect.release();
     }
 }
diff --git a/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/SoundTriggerHalEnforcer.java b/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/SoundTriggerHalEnforcer.java
index c3e0a3c..0f63347 100644
--- a/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/SoundTriggerHalEnforcer.java
+++ b/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/SoundTriggerHalEnforcer.java
@@ -27,7 +27,7 @@
 import android.media.soundtrigger_middleware.RecognitionEventSys;
 import android.os.DeadObjectException;
 import android.os.IBinder;
-import android.util.Log;
+import android.util.Slog;
 
 import java.util.HashMap;
 import java.util.Map;
@@ -227,10 +227,10 @@
         }
         if (e.getCause() instanceof DeadObjectException) {
             // Server is dead, no need to reboot.
-            Log.e(TAG, "HAL died");
+            Slog.e(TAG, "HAL died");
             throw new RecoverableException(Status.DEAD_OBJECT);
         }
-        Log.e(TAG, "Exception caught from HAL, rebooting HAL");
+        Slog.e(TAG, "Exception caught from HAL, rebooting HAL");
         reboot();
         throw e;
     }
@@ -257,14 +257,14 @@
             synchronized (mModelStates) {
                 ModelState state = mModelStates.get(model);
                 if (state == null) {
-                    Log.wtfStack(TAG, "Unexpected recognition event for model: " + model);
+                    Slog.wtfStack(TAG, "Unexpected recognition event for model: " + model);
                     reboot();
                     return;
                 }
                 if (event.recognitionEvent.recognitionStillActive
                         && event.recognitionEvent.status != RecognitionStatus.SUCCESS
                         && event.recognitionEvent.status != RecognitionStatus.FORCED) {
-                    Log.wtfStack(TAG,
+                    Slog.wtfStack(TAG,
                             "recognitionStillActive is only allowed when the recognition status "
                                     + "is SUCCESS");
                     reboot();
@@ -283,14 +283,14 @@
             synchronized (mModelStates) {
                 ModelState state = mModelStates.get(model);
                 if (state == null) {
-                    Log.wtfStack(TAG, "Unexpected recognition event for model: " + model);
+                    Slog.wtfStack(TAG, "Unexpected recognition event for model: " + model);
                     reboot();
                     return;
                 }
                 if (event.phraseRecognitionEvent.common.recognitionStillActive
                         && event.phraseRecognitionEvent.common.status != RecognitionStatus.SUCCESS
                         && event.phraseRecognitionEvent.common.status != RecognitionStatus.FORCED) {
-                    Log.wtfStack(TAG,
+                    Slog.wtfStack(TAG,
                             "recognitionStillActive is only allowed when the recognition status "
                                     + "is SUCCESS");
                     reboot();
@@ -309,13 +309,13 @@
             synchronized (mModelStates) {
                 ModelState state = mModelStates.get(modelHandle);
                 if (state == null) {
-                    Log.wtfStack(TAG, "Unexpected unload event for model: " + modelHandle);
+                    Slog.wtfStack(TAG, "Unexpected unload event for model: " + modelHandle);
                     reboot();
                     return;
                 }
 
                 if (state == ModelState.ACTIVE) {
-                    Log.wtfStack(TAG, "Trying to unload an active model: " + modelHandle);
+                    Slog.wtfStack(TAG, "Trying to unload an active model: " + modelHandle);
                     reboot();
                     return;
                 }
diff --git a/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/SoundTriggerHalWatchdog.java b/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/SoundTriggerHalWatchdog.java
index 0390f03..5e525e0 100644
--- a/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/SoundTriggerHalWatchdog.java
+++ b/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/SoundTriggerHalWatchdog.java
@@ -23,7 +23,7 @@
 import android.media.soundtrigger.RecognitionConfig;
 import android.media.soundtrigger.SoundModel;
 import android.os.IBinder;
-import android.util.Log;
+import android.util.Slog;
 
 import java.util.Objects;
 
@@ -172,7 +172,7 @@
 
         Watchdog() {
             mTask = mTimer.createTask(() -> {
-                Log.e(TAG, "HAL deadline expired. Rebooting.", mException);
+                Slog.e(TAG, "HAL deadline expired. Rebooting.", mException);
                 reboot();
             }, TIMEOUT_MS);
         }
diff --git a/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/SoundTriggerHw2Compat.java b/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/SoundTriggerHw2Compat.java
index df2e9b4..730e92c 100644
--- a/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/SoundTriggerHw2Compat.java
+++ b/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/SoundTriggerHw2Compat.java
@@ -32,7 +32,7 @@
 import android.os.RemoteException;
 import android.os.SystemClock;
 import android.system.OsConstants;
-import android.util.Log;
+import android.util.Slog;
 
 import java.io.IOException;
 import java.util.HashMap;
@@ -240,7 +240,7 @@
                 try {
                     hidlModel.data.close();
                 } catch (IOException e) {
-                    Log.e(TAG, "Failed to close file", e);
+                    Slog.e(TAG, "Failed to close file", e);
                 }
             }
         }
@@ -276,7 +276,7 @@
                 try {
                     hidlModel.common.data.close();
                 } catch (IOException e) {
-                    Log.e(TAG, "Failed to close file", e);
+                    Slog.e(TAG, "Failed to close file", e);
                 }
             }
         }
diff --git a/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareImpl.java b/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareImpl.java
index 3b800de..5a064da 100644
--- a/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareImpl.java
+++ b/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareImpl.java
@@ -20,7 +20,7 @@
 import android.media.soundtrigger_middleware.ISoundTriggerCallback;
 import android.media.soundtrigger_middleware.ISoundTriggerModule;
 import android.media.soundtrigger_middleware.SoundTriggerModuleDescriptor;
-import android.util.Log;
+import android.util.Slog;
 
 import java.util.ArrayList;
 import java.util.List;
@@ -85,7 +85,7 @@
             try {
                 modules.add(new SoundTriggerModule(halFactory, audioSessionProvider));
             } catch (Exception e) {
-                Log.e(TAG, "Failed to add a SoundTriggerModule instance", e);
+                Slog.e(TAG, "Failed to add a SoundTriggerModule instance", e);
             }
         }
 
diff --git a/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareValidation.java b/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareValidation.java
index 7ec2d9f..0b9ed8c 100644
--- a/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareValidation.java
+++ b/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareValidation.java
@@ -36,7 +36,7 @@
 import android.os.IBinder;
 import android.os.RemoteException;
 import android.os.ServiceSpecificException;
-import android.util.Log;
+import android.util.Slog;
 import android.util.SparseArray;
 
 import com.android.internal.util.Preconditions;
@@ -150,7 +150,7 @@
                     e.getMessage());
         }
 
-        Log.wtf(TAG, "Unexpected exception", e);
+        Slog.wtf(TAG, "Unexpected exception", e);
         throw new ServiceSpecificException(Status.INTERNAL_ERROR, e.getMessage());
     }
 
@@ -701,7 +701,7 @@
                 try {
                     mCallback.onRecognition(modelHandle, event, captureSession);
                 } catch (Exception e) {
-                    Log.w(TAG, "Client callback exception.", e);
+                    Slog.w(TAG, "Client callback exception.", e);
                }
             }
 
@@ -719,7 +719,7 @@
                 try {
                     mCallback.onPhraseRecognition(modelHandle, event, captureSession);
                 } catch (Exception e) {
-                    Log.w(TAG, "Client callback exception.", e);
+                    Slog.w(TAG, "Client callback exception.", e);
                }
             }
 
@@ -734,7 +734,7 @@
                 try {
                     mCallback.onModelUnloaded(modelHandle);
                 } catch (Exception e) {
-                    Log.w(TAG, "Client callback exception.", e);
+                    Slog.w(TAG, "Client callback exception.", e);
                 }
             }
 
@@ -746,7 +746,7 @@
                 } catch (RemoteException e) {
                     // Dead client will be handled by binderDied() - no need to handle here.
                     // In any case, client callbacks are considered best effort.
-                    Log.e(TAG, "Client callback exception.", e);
+                    Slog.e(TAG, "Client callback exception.", e);
                 }
             }
 
@@ -761,7 +761,7 @@
                 } catch (RemoteException e) {
                     // Dead client will be handled by binderDied() - no need to handle here.
                     // In any case, client callbacks are considered best effort.
-                    Log.e(TAG, "Client callback exception.", e);
+                    Slog.e(TAG, "Client callback exception.", e);
                 }
             }
 
@@ -795,11 +795,11 @@
                    // Check if state updated unexpectedly to log race conditions.
                     for (Map.Entry<Integer, ModelState> entry : mLoadedModels.entrySet()) {
                         if (cachedMap.get(entry.getKey()) != entry.getValue().activityState) {
-                            Log.e(TAG, "Unexpected state update in binderDied. Race occurred!");
+                            Slog.e(TAG, "Unexpected state update in binderDied. Race occurred!");
                         }
                     }
                     if (mLoadedModels.size() != cachedMap.size()) {
-                        Log.e(TAG, "Unexpected state update in binderDied. Race occurred!");
+                        Slog.e(TAG, "Unexpected state update in binderDied. Race occurred!");
                     }
                     try {
                         // Detach
diff --git a/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/SoundTriggerModule.java b/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/SoundTriggerModule.java
index e793f31..45a7faf 100644
--- a/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/SoundTriggerModule.java
+++ b/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/SoundTriggerModule.java
@@ -31,7 +31,7 @@
 import android.os.Binder;
 import android.os.IBinder;
 import android.os.RemoteException;
-import android.util.Log;
+import android.util.Slog;
 
 import java.util.ArrayList;
 import java.util.HashMap;
@@ -136,7 +136,7 @@
 
     @Override
     public void binderDied() {
-        Log.w(TAG, "Underlying HAL driver died.");
+        Slog.w(TAG, "Underlying HAL driver died.");
         List<ISoundTriggerCallback> callbacks;
         synchronized (this) {
             callbacks = new ArrayList<>(mActiveSessions.size());
@@ -270,7 +270,7 @@
                     try {
                         mAudioSessionProvider.releaseSession(audioSession.mSessionHandle);
                     } catch (Exception ee) {
-                        Log.e(TAG, "Failed to release session.", ee);
+                        Slog.e(TAG, "Failed to release session.", ee);
                     }
                     throw e;
                 }
@@ -286,7 +286,7 @@
                     checkValid();
                     Model loadedModel = new Model();
                     int result = loadedModel.load(model, audioSession);
-                    Log.d(TAG, String.format("loadPhraseModel()->%d", result));
+                    Slog.d(TAG, String.format("loadPhraseModel()->%d", result));
                     return result;
                 } catch (Exception e) {
                     // We must do this outside the lock, to avoid possible deadlocks with the remote
@@ -294,7 +294,7 @@
                     try {
                         mAudioSessionProvider.releaseSession(audioSession.mSessionHandle);
                     } catch (Exception ee) {
-                        Log.e(TAG, "Failed to release session.", ee);
+                        Slog.e(TAG, "Failed to release session.", ee);
                     }
                     throw e;
                 }