Merge "Add leadDisplayAddress to display-layout-config" into udc-qpr-dev
diff --git a/core/java/android/app/IActivityTaskManager.aidl b/core/java/android/app/IActivityTaskManager.aidl
index 4e2b6fa..d189bab 100644
--- a/core/java/android/app/IActivityTaskManager.aidl
+++ b/core/java/android/app/IActivityTaskManager.aidl
@@ -24,7 +24,6 @@
 import android.app.IApplicationThread;
 import android.app.IActivityClientController;
 import android.app.IActivityController;
-import android.app.IAppTask;
 import android.app.IAssistDataReceiver;
 import android.app.IInstrumentationWatcher;
 import android.app.IProcessObserver;
@@ -107,14 +106,6 @@
             in ProfilerInfo profilerInfo, in Bundle options, int userId);
     boolean startNextMatchingActivity(in IBinder callingActivity,
             in Intent intent, in Bundle options);
-
-    /**
-    *  The DreamActivity has to be started in a special way that does not involve the PackageParser.
-    *  The DreamActivity is a framework component inserted in the dream application process. Hence,
-    *  it is not declared in the application's manifest and cannot be parsed. startDreamActivity
-    *  creates the activity and starts it without reaching out to the PackageParser.
-    */
-    boolean startDreamActivity(in Intent intent);
     int startActivityIntentSender(in IApplicationThread caller,
             in IIntentSender target, in IBinder whitelistToken, in Intent fillInIntent,
             in String resolvedType, in IBinder resultTo, in String resultWho, int requestCode,
diff --git a/core/java/android/app/IUiAutomationConnection.aidl b/core/java/android/app/IUiAutomationConnection.aidl
index fbb0748..63cae63 100644
--- a/core/java/android/app/IUiAutomationConnection.aidl
+++ b/core/java/android/app/IUiAutomationConnection.aidl
@@ -24,6 +24,8 @@
 import android.view.WindowContentFrameStats;
 import android.view.WindowAnimationFrameStats;
 import android.os.ParcelFileDescriptor;
+import android.window.ScreenCapture.ScreenCaptureListener;
+import android.window.ScreenCapture.LayerCaptureArgs;
 
 import java.util.List;
 
@@ -43,8 +45,8 @@
     void injectInputEventToInputFilter(in InputEvent event);
     void syncInputTransactions(boolean waitForAnimations);
     boolean setRotation(int rotation);
-    Bitmap takeScreenshot(in Rect crop);
-    Bitmap takeSurfaceControlScreenshot(in SurfaceControl surfaceControl);
+    boolean takeScreenshot(in Rect crop, in ScreenCaptureListener listener);
+    boolean takeSurfaceControlScreenshot(in SurfaceControl surfaceControl, in ScreenCaptureListener listener);
     boolean clearWindowContentFrameStats(int windowId);
     WindowContentFrameStats getWindowContentFrameStats(int windowId);
     void clearWindowAnimationFrameStats();
diff --git a/core/java/android/app/NotificationManager.java b/core/java/android/app/NotificationManager.java
index 785470f..79b68c1 100644
--- a/core/java/android/app/NotificationManager.java
+++ b/core/java/android/app/NotificationManager.java
@@ -571,6 +571,12 @@
      */
     public static final int BUBBLE_PREFERENCE_SELECTED = 2;
 
+    /**
+     * Maximum length of the component name of a registered NotificationListenerService.
+     * @hide
+     */
+    public static int MAX_SERVICE_COMPONENT_NAME_LENGTH = 500;
+
     @UnsupportedAppUsage
     private static INotificationManager sService;
 
diff --git a/core/java/android/app/UiAutomation.java b/core/java/android/app/UiAutomation.java
index 247d5bc..c3d26d4 100644
--- a/core/java/android/app/UiAutomation.java
+++ b/core/java/android/app/UiAutomation.java
@@ -37,6 +37,7 @@
 import android.graphics.Point;
 import android.graphics.Rect;
 import android.graphics.Region;
+import android.hardware.HardwareBuffer;
 import android.hardware.display.DisplayManagerGlobal;
 import android.os.Build;
 import android.os.Handler;
@@ -71,6 +72,8 @@
 import android.view.accessibility.AccessibilityWindowInfo;
 import android.view.accessibility.IAccessibilityInteractionConnection;
 import android.view.inputmethod.EditorInfo;
+import android.window.ScreenCapture;
+import android.window.ScreenCapture.ScreenshotHardwareBuffer;
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
@@ -1160,17 +1163,12 @@
         Point displaySize = new Point();
         display.getRealSize(displaySize);
 
-        int rotation = display.getRotation();
-
         // Take the screenshot
-        Bitmap screenShot = null;
+        ScreenCapture.SynchronousScreenCaptureListener syncScreenCapture =
+                ScreenCapture.createSyncCaptureListener();
         try {
-            // Calling out without a lock held.
-            screenShot = mUiAutomationConnection.takeScreenshot(
-                    new Rect(0, 0, displaySize.x, displaySize.y));
-            if (screenShot == null) {
-                Log.e(LOG_TAG, "mUiAutomationConnection.takeScreenshot() returned null for display "
-                        + mDisplayId);
+            if (!mUiAutomationConnection.takeScreenshot(
+                    new Rect(0, 0, displaySize.x, displaySize.y), syncScreenCapture)) {
                 return null;
             }
         } catch (RemoteException re) {
@@ -1178,10 +1176,23 @@
             return null;
         }
 
-        // Optimization
-        screenShot.setHasAlpha(false);
+        final ScreenshotHardwareBuffer screenshotBuffer =
+                syncScreenCapture.getBuffer();
+        Bitmap screenShot = screenshotBuffer.asBitmap();
+        if (screenShot == null) {
+            Log.e(LOG_TAG, "mUiAutomationConnection.takeScreenshot() returned null for display "
+                    + mDisplayId);
+            return null;
+        }
+        Bitmap swBitmap;
+        try (HardwareBuffer buffer = screenshotBuffer.getHardwareBuffer()) {
+            swBitmap = screenShot.copy(Bitmap.Config.ARGB_8888, false);
+        }
+        screenShot.recycle();
 
-        return screenShot;
+        // Optimization
+        swBitmap.setHasAlpha(false);
+        return swBitmap;
     }
 
     /**
@@ -1218,12 +1229,27 @@
         // Apply a sync transaction to ensure SurfaceFlinger is flushed before capturing a
         // screenshot.
         new SurfaceControl.Transaction().apply(true);
+        ScreenCapture.SynchronousScreenCaptureListener syncScreenCapture =
+                ScreenCapture.createSyncCaptureListener();
         try {
-            return mUiAutomationConnection.takeSurfaceControlScreenshot(sc);
+            if (!mUiAutomationConnection.takeSurfaceControlScreenshot(sc, syncScreenCapture)) {
+                return null;
+            }
+
         } catch (RemoteException re) {
             Log.e(LOG_TAG, "Error while taking screenshot!", re);
             return null;
         }
+        ScreenCapture.ScreenshotHardwareBuffer captureBuffer =
+                syncScreenCapture.getBuffer();
+        Bitmap screenShot = captureBuffer.asBitmap();
+        Bitmap swBitmap;
+        try (HardwareBuffer buffer = captureBuffer.getHardwareBuffer()) {
+            swBitmap = screenShot.copy(Bitmap.Config.ARGB_8888, false);
+        }
+
+        screenShot.recycle();
+        return swBitmap;
     }
 
     /**
diff --git a/core/java/android/app/UiAutomationConnection.java b/core/java/android/app/UiAutomationConnection.java
index 34f0964..52949d6 100644
--- a/core/java/android/app/UiAutomationConnection.java
+++ b/core/java/android/app/UiAutomationConnection.java
@@ -25,7 +25,6 @@
 import android.annotation.UserIdInt;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.content.Context;
-import android.graphics.Bitmap;
 import android.graphics.Rect;
 import android.hardware.input.InputManager;
 import android.hardware.input.InputManagerGlobal;
@@ -51,8 +50,6 @@
 import android.view.accessibility.IAccessibilityManager;
 import android.window.ScreenCapture;
 import android.window.ScreenCapture.CaptureArgs;
-import android.window.ScreenCapture.ScreenshotHardwareBuffer;
-import android.window.ScreenCapture.SynchronousScreenCaptureListener;
 
 import libcore.io.IoUtils;
 
@@ -224,56 +221,54 @@
     }
 
     @Override
-    public Bitmap takeScreenshot(Rect crop) {
+    public boolean takeScreenshot(Rect crop, ScreenCapture.ScreenCaptureListener listener) {
         synchronized (mLock) {
             throwIfCalledByNotTrustedUidLocked();
             throwIfShutdownLocked();
             throwIfNotConnectedLocked();
         }
+
         final long identity = Binder.clearCallingIdentity();
         try {
             final CaptureArgs captureArgs = new CaptureArgs.Builder<>()
                     .setSourceCrop(crop)
                     .build();
-            SynchronousScreenCaptureListener syncScreenCapture =
-                    ScreenCapture.createSyncCaptureListener();
-            mWindowManager.captureDisplay(DEFAULT_DISPLAY, captureArgs,
-                    syncScreenCapture);
-            final ScreenshotHardwareBuffer screenshotBuffer =
-                    syncScreenCapture.getBuffer();
-            return screenshotBuffer == null ? null : screenshotBuffer.asBitmap();
+            mWindowManager.captureDisplay(DEFAULT_DISPLAY, captureArgs, listener);
         } catch (RemoteException re) {
             re.rethrowAsRuntimeException();
         } finally {
             Binder.restoreCallingIdentity(identity);
         }
-        return null;
+
+        return true;
     }
 
     @Nullable
     @Override
-    public Bitmap takeSurfaceControlScreenshot(@NonNull SurfaceControl surfaceControl) {
+    public boolean takeSurfaceControlScreenshot(@NonNull SurfaceControl surfaceControl,
+            ScreenCapture.ScreenCaptureListener listener) {
         synchronized (mLock) {
             throwIfCalledByNotTrustedUidLocked();
             throwIfShutdownLocked();
             throwIfNotConnectedLocked();
         }
 
-        ScreenCapture.ScreenshotHardwareBuffer captureBuffer;
         final long identity = Binder.clearCallingIdentity();
         try {
-            captureBuffer = ScreenCapture.captureLayers(
+            ScreenCapture.LayerCaptureArgs args =
                     new ScreenCapture.LayerCaptureArgs.Builder(surfaceControl)
-                            .setChildrenOnly(false)
-                            .build());
+                    .setChildrenOnly(false)
+                    .build();
+            int status = ScreenCapture.captureLayers(args, listener);
+
+            if (status != 0) {
+                return false;
+            }
         } finally {
             Binder.restoreCallingIdentity(identity);
         }
 
-        if (captureBuffer == null) {
-            return null;
-        }
-        return captureBuffer.asBitmap();
+        return true;
     }
 
     @Override
diff --git a/core/java/android/app/admin/DevicePolicyManagerInternal.java b/core/java/android/app/admin/DevicePolicyManagerInternal.java
index 0e78275..8dd50f0 100644
--- a/core/java/android/app/admin/DevicePolicyManagerInternal.java
+++ b/core/java/android/app/admin/DevicePolicyManagerInternal.java
@@ -250,6 +250,16 @@
     public abstract ComponentName getProfileOwnerAsUser(@UserIdInt int userId);
 
     /**
+     * Returns the device owner component for the device, or {@code null} if there is not one.
+     *
+     * @deprecated added temporarily to support Android Role permission granting.
+     * Please contact Android Enterprise Device Policy team before calling this function.
+     */
+    @Deprecated
+    @Nullable
+    public abstract ComponentName getDeviceOwnerComponent(boolean callingUserOnly);
+
+    /**
      * Returns the user id of the device owner, or {@link UserHandle#USER_NULL} if there is not one.
      */
     @UserIdInt
diff --git a/core/java/android/content/BroadcastReceiver.java b/core/java/android/content/BroadcastReceiver.java
index 3d76b28..d7195a7 100644
--- a/core/java/android/content/BroadcastReceiver.java
+++ b/core/java/android/content/BroadcastReceiver.java
@@ -65,7 +65,7 @@
      * thread of your app.
      *
      * <p>Note on threading: the state inside of this class is not itself
-     * thread-safe, however you can use it from any thread if you properly
+     * thread-safe. However, you can use it from any thread if you make
      * sure that you do not have races.  Typically this means you will hand
      * the entire object to another thread, which will be solely responsible
      * for setting any results and finally calling {@link #finish()}.
diff --git a/core/java/android/content/pm/IPackageManager.aidl b/core/java/android/content/pm/IPackageManager.aidl
index 47a5db8..3fdd023 100644
--- a/core/java/android/content/pm/IPackageManager.aidl
+++ b/core/java/android/content/pm/IPackageManager.aidl
@@ -770,6 +770,11 @@
 
     void setSplashScreenTheme(String packageName, String themeName, int userId);
 
+    int getUserMinAspectRatio(String packageName, int userId);
+
+    @EnforcePermission("INSTALL_PACKAGES")
+    void setUserMinAspectRatio(String packageName, int userId, int aspectRatio);
+
     List<String> getMimeGroup(String packageName, String group);
 
     boolean isAutoRevokeWhitelisted(String packageName);
diff --git a/core/java/android/content/pm/PackageInstaller.java b/core/java/android/content/pm/PackageInstaller.java
index 105b38a..4b883cd 100644
--- a/core/java/android/content/pm/PackageInstaller.java
+++ b/core/java/android/content/pm/PackageInstaller.java
@@ -3045,10 +3045,6 @@
          * The update ownership enforcement can only be enabled on initial installation. Set
          * this to {@code true} on package update is a no-op.
          *
-         * Apps may opt themselves out of update ownership by setting the
-         * <a href="https://developer.android.com/guide/topics/manifest/manifest-element.html#allowupdateownership">android:alllowUpdateOwnership</a>
-         * attribute in their manifest to <code>false</code>.
-         *
          * Note: To enable the update ownership enforcement, the installer must have the
          * {@link android.Manifest.permission#ENFORCE_UPDATE_OWNERSHIP ENFORCE_UPDATE_OWNERSHIP}
          * permission.
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index 8fafb18..66aadac 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -2342,6 +2342,64 @@
      */
     public static final int INSTALL_FAILED_SHARED_LIBRARY_BAD_CERTIFICATE_DIGEST = -130;
 
+    /**
+     * App minimum aspect ratio set by the user which will override app-defined aspect ratio.
+     *
+     * @hide
+     */
+    @IntDef(prefix = { "USER_MIN_ASPECT_RATIO_" }, value = {
+            USER_MIN_ASPECT_RATIO_UNSET,
+            USER_MIN_ASPECT_RATIO_SPLIT_SCREEN,
+            USER_MIN_ASPECT_RATIO_DISPLAY_SIZE,
+            USER_MIN_ASPECT_RATIO_4_3,
+            USER_MIN_ASPECT_RATIO_16_9,
+            USER_MIN_ASPECT_RATIO_3_2,
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface UserMinAspectRatio {}
+
+    /**
+     * No aspect ratio override has been set by user.
+     *
+     * @hide
+     */
+    public static final int USER_MIN_ASPECT_RATIO_UNSET = 0;
+
+    /**
+     * Aspect ratio override code: user forces app to split screen aspect ratio. This is adjusted to
+     * half of the screen without the split screen divider.
+     *
+     * @hide
+     */
+    public static final int USER_MIN_ASPECT_RATIO_SPLIT_SCREEN = 1;
+
+    /**
+     * Aspect ratio override code: user forces app to the aspect ratio of the device display size.
+     * This will be the portrait aspect ratio of the device if the app is portrait or the landscape
+     * aspect ratio of the device if the app is landscape.
+     *
+     * @hide
+     */
+    public static final int USER_MIN_ASPECT_RATIO_DISPLAY_SIZE = 2;
+
+    /**
+     * Aspect ratio override code: user forces app to 4:3 min aspect ratio
+     * @hide
+     */
+    public static final int USER_MIN_ASPECT_RATIO_4_3 = 3;
+
+    /**
+     * Aspect ratio override code: user forces app to 16:9 min aspect ratio
+     * @hide
+     */
+    public static final int USER_MIN_ASPECT_RATIO_16_9 = 4;
+
+    /**
+     * Aspect ratio override code: user forces app to 3:2 min aspect ratio
+     * @hide
+     */
+    public static final int USER_MIN_ASPECT_RATIO_3_2 = 5;
+
     /** @hide */
     @IntDef(flag = true, prefix = { "DELETE_" }, value = {
             DELETE_KEEP_DATA,
diff --git a/core/java/android/content/res/ThemedResourceCache.java b/core/java/android/content/res/ThemedResourceCache.java
index a7cd168..690dfcf 100644
--- a/core/java/android/content/res/ThemedResourceCache.java
+++ b/core/java/android/content/res/ThemedResourceCache.java
@@ -137,8 +137,10 @@
      */
     @UnsupportedAppUsage
     public void onConfigurationChange(@Config int configChanges) {
-        prune(configChanges);
-        mGeneration++;
+        synchronized (this) {
+            pruneLocked(configChanges);
+            mGeneration++;
+        }
     }
 
     /**
@@ -214,22 +216,20 @@
      *                      simply prune missing weak references
      * @return {@code true} if the cache is completely empty after pruning
      */
-    private boolean prune(@Config int configChanges) {
-        synchronized (this) {
-            if (mThemedEntries != null) {
-                for (int i = mThemedEntries.size() - 1; i >= 0; i--) {
-                    if (pruneEntriesLocked(mThemedEntries.valueAt(i), configChanges)) {
-                        mThemedEntries.removeAt(i);
-                    }
+    private boolean pruneLocked(@Config int configChanges) {
+        if (mThemedEntries != null) {
+            for (int i = mThemedEntries.size() - 1; i >= 0; i--) {
+                if (pruneEntriesLocked(mThemedEntries.valueAt(i), configChanges)) {
+                    mThemedEntries.removeAt(i);
                 }
             }
-
-            pruneEntriesLocked(mNullThemedEntries, configChanges);
-            pruneEntriesLocked(mUnthemedEntries, configChanges);
-
-            return mThemedEntries == null && mNullThemedEntries == null
-                    && mUnthemedEntries == null;
         }
+
+        pruneEntriesLocked(mNullThemedEntries, configChanges);
+        pruneEntriesLocked(mUnthemedEntries, configChanges);
+
+        return mThemedEntries == null && mNullThemedEntries == null
+                && mUnthemedEntries == null;
     }
 
     private boolean pruneEntriesLocked(@Nullable LongSparseArray<WeakReference<T>> entries,
diff --git a/core/java/android/hardware/camera2/CameraExtensionCharacteristics.java b/core/java/android/hardware/camera2/CameraExtensionCharacteristics.java
index d48e20e..6baf91d7 100644
--- a/core/java/android/hardware/camera2/CameraExtensionCharacteristics.java
+++ b/core/java/android/hardware/camera2/CameraExtensionCharacteristics.java
@@ -34,6 +34,7 @@
 import android.hardware.camera2.impl.CameraMetadataNative;
 import android.hardware.camera2.params.ExtensionSessionConfiguration;
 import android.hardware.camera2.params.StreamConfigurationMap;
+import android.os.Binder;
 import android.os.ConditionVariable;
 import android.os.IBinder;
 import android.os.RemoteException;
@@ -48,7 +49,6 @@
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
-import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
@@ -346,28 +346,29 @@
             }
         }
 
-        public long registerClient(Context ctx) {
+        public boolean registerClient(Context ctx, IBinder token) {
             synchronized (mLock) {
                 connectToProxyLocked(ctx);
-                if (mProxy != null) {
-                    try {
-                        return mProxy.registerClient();
-                    } catch (RemoteException e) {
-                        Log.e(TAG, "Failed to initialize extension! Extension service does "
-                                + " not respond!");
-                        return -1;
-                    }
-                } else {
-                    return -1;
+                if (mProxy == null) {
+                    return false;
                 }
+
+                try {
+                    return mProxy.registerClient(token);
+                } catch (RemoteException e) {
+                    Log.e(TAG, "Failed to initialize extension! Extension service does "
+                            + " not respond!");
+                }
+
+                return false;
             }
         }
 
-        public void unregisterClient(long clientId) {
+        public void unregisterClient(IBinder token) {
             synchronized (mLock) {
                 if (mProxy != null) {
                     try {
-                        mProxy.unregisterClient(clientId);
+                        mProxy.unregisterClient(token);
                     } catch (RemoteException e) {
                         Log.e(TAG, "Failed to de-initialize extension! Extension service does"
                                 + " not respond!");
@@ -438,15 +439,15 @@
     /**
      * @hide
      */
-    public static long registerClient(Context ctx) {
-        return CameraExtensionManagerGlobal.get().registerClient(ctx);
+    public static boolean registerClient(Context ctx, IBinder token) {
+        return CameraExtensionManagerGlobal.get().registerClient(ctx, token);
     }
 
     /**
      * @hide
      */
-    public static void unregisterClient(long clientId) {
-        CameraExtensionManagerGlobal.get().unregisterClient(clientId);
+    public static void unregisterClient(IBinder token) {
+        CameraExtensionManagerGlobal.get().unregisterClient(token);
     }
 
     /**
@@ -564,8 +565,9 @@
      */
     public @NonNull List<Integer> getSupportedExtensions() {
         ArrayList<Integer> ret = new ArrayList<>();
-        long clientId = registerClient(mContext);
-        if (clientId < 0) {
+        final IBinder token = new Binder(TAG + "#getSupportedExtensions:" + mCameraId);
+        boolean success = registerClient(mContext, token);
+        if (!success) {
             return Collections.unmodifiableList(ret);
         }
 
@@ -576,7 +578,7 @@
                 }
             }
         } finally {
-            unregisterClient(clientId);
+            unregisterClient(token);
         }
 
         return Collections.unmodifiableList(ret);
@@ -599,8 +601,9 @@
      * supported device-specific extension
      */
     public boolean isPostviewAvailable(@Extension int extension) {
-        long clientId = registerClient(mContext);
-        if (clientId < 0) {
+        final IBinder token = new Binder(TAG + "#isPostviewAvailable:" + mCameraId);
+        boolean success = registerClient(mContext, token);
+        if (!success) {
             throw new IllegalArgumentException("Unsupported extensions");
         }
 
@@ -623,7 +626,7 @@
             Log.e(TAG, "Failed to query the extension for postview availability! Extension "
                     + "service does not respond!");
         } finally {
-            unregisterClient(clientId);
+            unregisterClient(token);
         }
 
         return false;
@@ -656,9 +659,9 @@
     @NonNull
     public List<Size> getPostviewSupportedSizes(@Extension int extension,
             @NonNull Size captureSize, int format) {
-
-        long clientId = registerClient(mContext);
-        if (clientId < 0) {
+        final IBinder token = new Binder(TAG + "#getPostviewSupportedSizes:" + mCameraId);
+        boolean success = registerClient(mContext, token);
+        if (!success) {
             throw new IllegalArgumentException("Unsupported extensions");
         }
 
@@ -719,7 +722,7 @@
                     + "service does not respond!");
             return Collections.emptyList();
         } finally {
-            unregisterClient(clientId);
+            unregisterClient(token);
         }
     }
 
@@ -756,8 +759,9 @@
         // TODO: Revisit this code once the Extension preview processor output format
         //       ambiguity is resolved in b/169799538.
 
-        long clientId = registerClient(mContext);
-        if (clientId < 0) {
+        final IBinder token = new Binder(TAG + "#getExtensionSupportedSizes:" + mCameraId);
+        boolean success = registerClient(mContext, token);
+        if (!success) {
             throw new IllegalArgumentException("Unsupported extensions");
         }
 
@@ -787,7 +791,7 @@
                     + " not respond!");
             return new ArrayList<>();
         } finally {
-            unregisterClient(clientId);
+            unregisterClient(token);
         }
     }
 
@@ -814,8 +818,9 @@
     public @NonNull
     List<Size> getExtensionSupportedSizes(@Extension int extension, int format) {
         try {
-            long clientId = registerClient(mContext);
-            if (clientId < 0) {
+            final IBinder token = new Binder(TAG + "#getExtensionSupportedSizes:" + mCameraId);
+            boolean success = registerClient(mContext, token);
+            if (!success) {
                 throw new IllegalArgumentException("Unsupported extensions");
             }
 
@@ -867,7 +872,7 @@
                     }
                 }
             } finally {
-                unregisterClient(clientId);
+                unregisterClient(token);
             }
         } catch (RemoteException e) {
             Log.e(TAG, "Failed to query the extension supported sizes! Extension service does"
@@ -888,7 +893,6 @@
      * @param format            device-specific extension output format
      * @return the range of estimated minimal and maximal capture latency in milliseconds
      * or null if no capture latency info can be provided
-     *
      * @throws IllegalArgumentException in case of format different from {@link ImageFormat#JPEG} /
      *                                  {@link ImageFormat#YUV_420_888}; or unsupported extension.
      */
@@ -903,8 +907,9 @@
                 throw new IllegalArgumentException("Unsupported format: " + format);
         }
 
-        long clientId = registerClient(mContext);
-        if (clientId < 0) {
+        final IBinder token = new Binder(TAG + "#getEstimatedCaptureLatencyRangeMillis:" + mCameraId);
+        boolean success = registerClient(mContext, token);
+        if (!success) {
             throw new IllegalArgumentException("Unsupported extensions");
         }
 
@@ -952,7 +957,7 @@
             Log.e(TAG, "Failed to query the extension capture latency! Extension service does"
                     + " not respond!");
         } finally {
-            unregisterClient(clientId);
+            unregisterClient(token);
         }
 
         return null;
@@ -968,8 +973,9 @@
      * @throws IllegalArgumentException in case of an unsupported extension.
      */
     public boolean isCaptureProcessProgressAvailable(@Extension int extension) {
-        long clientId = registerClient(mContext);
-        if (clientId < 0) {
+        final IBinder token = new Binder(TAG + "#isCaptureProcessProgressAvailable:" + mCameraId);
+        boolean success = registerClient(mContext, token);
+        if (!success) {
             throw new IllegalArgumentException("Unsupported extensions");
         }
 
@@ -992,7 +998,7 @@
             Log.e(TAG, "Failed to query the extension progress callbacks! Extension service does"
                     + " not respond!");
         } finally {
-            unregisterClient(clientId);
+            unregisterClient(token);
         }
 
         return false;
@@ -1013,8 +1019,9 @@
      */
     @NonNull
     public Set<CaptureRequest.Key> getAvailableCaptureRequestKeys(@Extension int extension) {
-        long clientId = registerClient(mContext);
-        if (clientId < 0) {
+        final IBinder token = new Binder(TAG + "#getAvailableCaptureRequestKeys:" + mCameraId);
+        boolean success = registerClient(mContext, token);
+        if (!success) {
             throw new IllegalArgumentException("Unsupported extensions");
         }
 
@@ -1033,10 +1040,11 @@
             } else {
                 Pair<IPreviewExtenderImpl, IImageCaptureExtenderImpl> extenders =
                         initializeExtension(extension);
-                extenders.second.onInit(mCameraId, mCharacteristicsMapNative.get(mCameraId));
+                extenders.second.onInit(token, mCameraId,
+                        mCharacteristicsMapNative.get(mCameraId));
                 extenders.second.init(mCameraId, mCharacteristicsMapNative.get(mCameraId));
                 captureRequestMeta = extenders.second.getAvailableCaptureRequestKeys();
-                extenders.second.onDeInit();
+                extenders.second.onDeInit(token);
             }
 
             if (captureRequestMeta != null) {
@@ -1067,7 +1075,7 @@
         } catch (RemoteException e) {
             throw new IllegalStateException("Failed to query the available capture request keys!");
         } finally {
-            unregisterClient(clientId);
+            unregisterClient(token);
         }
 
         return Collections.unmodifiableSet(ret);
@@ -1092,8 +1100,9 @@
      */
     @NonNull
     public Set<CaptureResult.Key> getAvailableCaptureResultKeys(@Extension int extension) {
-        long clientId = registerClient(mContext);
-        if (clientId < 0) {
+        final IBinder token = new Binder(TAG + "#getAvailableCaptureResultKeys:" + mCameraId);
+        boolean success = registerClient(mContext, token);
+        if (!success) {
             throw new IllegalArgumentException("Unsupported extensions");
         }
 
@@ -1111,10 +1120,11 @@
             } else {
                 Pair<IPreviewExtenderImpl, IImageCaptureExtenderImpl> extenders =
                         initializeExtension(extension);
-                extenders.second.onInit(mCameraId, mCharacteristicsMapNative.get(mCameraId));
+                extenders.second.onInit(token, mCameraId,
+                        mCharacteristicsMapNative.get(mCameraId));
                 extenders.second.init(mCameraId, mCharacteristicsMapNative.get(mCameraId));
                 captureResultMeta = extenders.second.getAvailableCaptureResultKeys();
-                extenders.second.onDeInit();
+                extenders.second.onDeInit(token);
             }
 
             if (captureResultMeta != null) {
@@ -1126,7 +1136,7 @@
                 }
                 CameraCharacteristics resultChars = new CameraCharacteristics(captureResultMeta);
                 Object crKey = CaptureResult.Key.class;
-                Class<CaptureResult.Key<?>> crKeyTyped = (Class<CaptureResult.Key<?>>)crKey;
+                Class<CaptureResult.Key<?>> crKeyTyped = (Class<CaptureResult.Key<?>>) crKey;
 
                 ret.addAll(resultChars.getAvailableKeyList(CaptureResult.class, crKeyTyped,
                         resultKeys, /*includeSynthetic*/ true));
@@ -1145,7 +1155,7 @@
         } catch (RemoteException e) {
             throw new IllegalStateException("Failed to query the available capture result keys!");
         } finally {
-            unregisterClient(clientId);
+            unregisterClient(token);
         }
 
         return Collections.unmodifiableSet(ret);
diff --git a/core/java/android/hardware/camera2/CameraManager.java b/core/java/android/hardware/camera2/CameraManager.java
index 85f8ca6..a098362 100644
--- a/core/java/android/hardware/camera2/CameraManager.java
+++ b/core/java/android/hardware/camera2/CameraManager.java
@@ -67,6 +67,7 @@
 import java.util.Arrays;
 import java.util.Comparator;
 import java.util.HashMap;
+import java.util.Iterator;
 import java.util.Map;
 import java.util.Set;
 import java.util.concurrent.Executor;
@@ -178,22 +179,20 @@
             boolean folded = ArrayUtils.contains(mFoldedDeviceStates, state);
 
             mFoldedDeviceState = folded;
-            ArrayList<WeakReference<DeviceStateListener>> invalidListeners = new ArrayList<>();
-            for (WeakReference<DeviceStateListener> listener : mDeviceStateListeners) {
-                DeviceStateListener callback = listener.get();
+            Iterator<WeakReference<DeviceStateListener>> it = mDeviceStateListeners.iterator();
+            while(it.hasNext()) {
+                DeviceStateListener callback = it.next().get();
                 if (callback != null) {
                     callback.onDeviceStateChanged(folded);
                 } else {
-                    invalidListeners.add(listener);
+                    it.remove();
                 }
             }
-            if (!invalidListeners.isEmpty()) {
-                mDeviceStateListeners.removeAll(invalidListeners);
-            }
         }
 
         public synchronized void addDeviceStateListener(DeviceStateListener listener) {
             listener.onDeviceStateChanged(mFoldedDeviceState);
+            mDeviceStateListeners.removeIf(l -> l.get() == null);
             mDeviceStateListeners.add(new WeakReference<>(listener));
         }
 
diff --git a/core/java/android/hardware/camera2/extension/ICameraExtensionsProxyService.aidl b/core/java/android/hardware/camera2/extension/ICameraExtensionsProxyService.aidl
index b52c6500..3b7d801 100644
--- a/core/java/android/hardware/camera2/extension/ICameraExtensionsProxyService.aidl
+++ b/core/java/android/hardware/camera2/extension/ICameraExtensionsProxyService.aidl
@@ -20,11 +20,13 @@
 import android.hardware.camera2.extension.IImageCaptureExtenderImpl;
 import android.hardware.camera2.extension.IInitializeSessionCallback;
 
+import android.os.IBinder;
+
 /** @hide */
 interface ICameraExtensionsProxyService
 {
-    long registerClient();
-    void unregisterClient(long clientId);
+    boolean registerClient(in IBinder token);
+    void unregisterClient(in IBinder token);
     boolean advancedExtensionsSupported();
     void initializeSession(in IInitializeSessionCallback cb);
     void releaseSession();
diff --git a/core/java/android/hardware/camera2/extension/IImageCaptureExtenderImpl.aidl b/core/java/android/hardware/camera2/extension/IImageCaptureExtenderImpl.aidl
index 754f8f6..5a22418 100644
--- a/core/java/android/hardware/camera2/extension/IImageCaptureExtenderImpl.aidl
+++ b/core/java/android/hardware/camera2/extension/IImageCaptureExtenderImpl.aidl
@@ -24,11 +24,13 @@
 import android.hardware.camera2.extension.Size;
 import android.hardware.camera2.extension.SizeList;
 
+import android.os.IBinder;
+
 /** @hide */
 interface IImageCaptureExtenderImpl
 {
-    void onInit(in String cameraId, in CameraMetadataNative cameraCharacteristics);
-    void onDeInit();
+    void onInit(in IBinder token, in String cameraId, in CameraMetadataNative cameraCharacteristics);
+    void onDeInit(in IBinder token);
     @nullable CaptureStageImpl onPresetSession();
     @nullable CaptureStageImpl onEnableSession();
     @nullable CaptureStageImpl onDisableSession();
diff --git a/core/java/android/hardware/camera2/extension/IPreviewExtenderImpl.aidl b/core/java/android/hardware/camera2/extension/IPreviewExtenderImpl.aidl
index 01046d0..9ea8a74 100644
--- a/core/java/android/hardware/camera2/extension/IPreviewExtenderImpl.aidl
+++ b/core/java/android/hardware/camera2/extension/IPreviewExtenderImpl.aidl
@@ -22,11 +22,13 @@
 import android.hardware.camera2.extension.IRequestUpdateProcessorImpl;
 import android.hardware.camera2.extension.SizeList;
 
+import android.os.IBinder;
+
 /** @hide */
 interface IPreviewExtenderImpl
 {
-    void onInit(in String cameraId, in CameraMetadataNative cameraCharacteristics);
-    void onDeInit();
+    void onInit(in IBinder token, in String cameraId, in CameraMetadataNative cameraCharacteristics);
+    void onDeInit(in IBinder token);
     @nullable CaptureStageImpl onPresetSession();
     @nullable CaptureStageImpl onEnableSession();
     @nullable CaptureStageImpl onDisableSession();
diff --git a/core/java/android/hardware/camera2/extension/ISessionProcessorImpl.aidl b/core/java/android/hardware/camera2/extension/ISessionProcessorImpl.aidl
index 13b93a8..0581ec0 100644
--- a/core/java/android/hardware/camera2/extension/ISessionProcessorImpl.aidl
+++ b/core/java/android/hardware/camera2/extension/ISessionProcessorImpl.aidl
@@ -25,13 +25,15 @@
 import android.hardware.camera2.extension.LatencyRange;
 import android.hardware.camera2.extension.OutputSurface;
 
+import android.os.IBinder;
+
 /** @hide */
 interface ISessionProcessorImpl
 {
-    CameraSessionConfig initSession(in String cameraId,
+    CameraSessionConfig initSession(in IBinder token, in String cameraId,
             in Map<String, CameraMetadataNative> charsMap, in OutputSurface previewSurface,
             in OutputSurface imageCaptureSurface, in OutputSurface postviewSurface);
-    void deInitSession();
+    void deInitSession(in IBinder token);
     void onCaptureSessionStart(IRequestProcessorImpl requestProcessor);
     void onCaptureSessionEnd();
     int startRepeating(in ICaptureCallback callback);
diff --git a/core/java/android/hardware/camera2/impl/CameraAdvancedExtensionSessionImpl.java b/core/java/android/hardware/camera2/impl/CameraAdvancedExtensionSessionImpl.java
index 65d4b43..ae700a0 100644
--- a/core/java/android/hardware/camera2/impl/CameraAdvancedExtensionSessionImpl.java
+++ b/core/java/android/hardware/camera2/impl/CameraAdvancedExtensionSessionImpl.java
@@ -60,6 +60,7 @@
 import android.os.Binder;
 import android.os.Handler;
 import android.os.HandlerThread;
+import android.os.IBinder;
 import android.os.RemoteException;
 import android.util.Log;
 import android.util.Size;
@@ -79,7 +80,6 @@
     private final Executor mExecutor;
     private CameraDevice mCameraDevice;
     private final Map<String, CameraMetadataNative> mCharacteristicsMap;
-    private final long mExtensionClientId;
     private final Handler mHandler;
     private final HandlerThread mHandlerThread;
     private final CameraExtensionSession.StateCallback mCallbacks;
@@ -90,6 +90,7 @@
     private final HashMap<Integer, ImageReader> mReaderMap = new HashMap<>();
     private RequestProcessor mRequestProcessor = new RequestProcessor();
     private final int mSessionId;
+    private final IBinder mToken;
 
     private Surface mClientRepeatingRequestSurface;
     private Surface mClientCaptureSurface;
@@ -114,8 +115,9 @@
             @NonNull Map<String, CameraCharacteristics> characteristicsMap,
             @NonNull Context ctx, @NonNull ExtensionSessionConfiguration config, int sessionId)
             throws CameraAccessException, RemoteException {
-        long clientId = CameraExtensionCharacteristics.registerClient(ctx);
-        if (clientId < 0) {
+        final IBinder token = new Binder(TAG + " : " + sessionId);
+        boolean success = CameraExtensionCharacteristics.registerClient(ctx, token);
+        if (!success) {
             throw new UnsupportedOperationException("Unsupported extension!");
         }
 
@@ -202,11 +204,10 @@
         IAdvancedExtenderImpl extender = CameraExtensionCharacteristics.initializeAdvancedExtension(
                 config.getExtension());
         extender.init(cameraId, characteristicsMapNative);
-
-        CameraAdvancedExtensionSessionImpl ret = new CameraAdvancedExtensionSessionImpl(clientId,
-                extender, cameraDevice, characteristicsMapNative, repeatingRequestSurface,
+        CameraAdvancedExtensionSessionImpl ret = new CameraAdvancedExtensionSessionImpl(extender,
+                cameraDevice, characteristicsMapNative, repeatingRequestSurface,
                 burstCaptureSurface, postviewSurface, config.getStateCallback(),
-                config.getExecutor(), sessionId);
+                config.getExecutor(), sessionId, token);
 
         ret.mStatsAggregator.setClientName(ctx.getOpPackageName());
         ret.mStatsAggregator.setExtensionType(config.getExtension());
@@ -216,15 +217,13 @@
         return ret;
     }
 
-    private CameraAdvancedExtensionSessionImpl(long extensionClientId,
-            @NonNull IAdvancedExtenderImpl extender,
+    private CameraAdvancedExtensionSessionImpl(@NonNull IAdvancedExtenderImpl extender,
             @NonNull CameraDeviceImpl cameraDevice,
             Map<String, CameraMetadataNative> characteristicsMap,
             @Nullable Surface repeatingRequestSurface, @Nullable Surface burstCaptureSurface,
             @Nullable Surface postviewSurface,
             @NonNull StateCallback callback, @NonNull Executor executor,
-            int sessionId) {
-        mExtensionClientId = extensionClientId;
+            int sessionId, @NonNull IBinder token) {
         mAdvancedExtender = extender;
         mCameraDevice = cameraDevice;
         mCharacteristicsMap = characteristicsMap;
@@ -240,6 +239,7 @@
         mSessionClosed = false;
         mInitializeHandler = new InitializeSessionHandler();
         mSessionId = sessionId;
+        mToken = token;
         mInterfaceLock = cameraDevice.mInterfaceLock;
 
         mStatsAggregator = new ExtensionSessionStatsAggregator(mCameraDevice.getId(),
@@ -260,7 +260,8 @@
         OutputSurface postviewSurface = initializeParcelable(mClientPostviewSurface);
 
         mSessionProcessor = mAdvancedExtender.getSessionProcessor();
-        CameraSessionConfig sessionConfig = mSessionProcessor.initSession(mCameraDevice.getId(),
+        CameraSessionConfig sessionConfig = mSessionProcessor.initSession(mToken,
+                mCameraDevice.getId(),
                 mCharacteristicsMap, previewSurface, captureSurface, postviewSurface);
         List<CameraOutputConfig> outputConfigs = sessionConfig.outputConfigs;
         ArrayList<OutputConfiguration> outputList = new ArrayList<>();
@@ -526,7 +527,11 @@
         synchronized (mInterfaceLock) {
             if (mInitialized) {
                 try {
-                    mCaptureSession.stopRepeating();
+                    try {
+                        mCaptureSession.stopRepeating();
+                    } catch (IllegalStateException e) {
+                        // OK: already be closed, nothing else to do
+                    }
                     mSessionProcessor.stopRepeating();
                     mSessionProcessor.onCaptureSessionEnd();
                     mSessionClosed = true;
@@ -565,7 +570,7 @@
                     if (!mSessionClosed) {
                         mSessionProcessor.onCaptureSessionEnd();
                     }
-                    mSessionProcessor.deInitSession();
+                    mSessionProcessor.deInitSession(mToken);
                 } catch (RemoteException e) {
                     Log.e(TAG, "Failed to de-initialize session processor, extension service"
                             + " does not respond!") ;
@@ -573,12 +578,10 @@
                 mSessionProcessor = null;
             }
 
-            if (mExtensionClientId >= 0) {
-                CameraExtensionCharacteristics.unregisterClient(mExtensionClientId);
-                if (mInitialized || (mCaptureSession != null)) {
-                    notifyClose = true;
-                    CameraExtensionCharacteristics.releaseSession();
-                }
+            CameraExtensionCharacteristics.unregisterClient(mToken);
+            if (mInitialized || (mCaptureSession != null)) {
+                notifyClose = true;
+                CameraExtensionCharacteristics.releaseSession();
             }
             mInitialized = false;
 
diff --git a/core/java/android/hardware/camera2/impl/CameraExtensionSessionImpl.java b/core/java/android/hardware/camera2/impl/CameraExtensionSessionImpl.java
index 9ebef0b..1db4808 100644
--- a/core/java/android/hardware/camera2/impl/CameraExtensionSessionImpl.java
+++ b/core/java/android/hardware/camera2/impl/CameraExtensionSessionImpl.java
@@ -56,6 +56,7 @@
 import android.os.Binder;
 import android.os.Handler;
 import android.os.HandlerThread;
+import android.os.IBinder;
 import android.os.RemoteException;
 import android.util.Log;
 import android.util.LongSparseArray;
@@ -79,7 +80,6 @@
 
     private final Executor mExecutor;
     private final CameraDevice mCameraDevice;
-    private final long mExtensionClientId;
     private final IImageCaptureExtenderImpl mImageExtender;
     private final IPreviewExtenderImpl mPreviewExtender;
     private final Handler mHandler;
@@ -91,6 +91,7 @@
     private final Set<CaptureRequest.Key> mSupportedRequestKeys;
     private final Set<CaptureResult.Key> mSupportedResultKeys;
     private final ExtensionSessionStatsAggregator mStatsAggregator;
+    private final IBinder mToken;
     private boolean mCaptureResultsSupported;
 
     private CameraCaptureSession mCaptureSession = null;
@@ -111,6 +112,7 @@
     private int mPreviewProcessorType = IPreviewExtenderImpl.PROCESSOR_TYPE_NONE;
 
     private boolean mInitialized;
+    private boolean mSessionClosed;
     // Enable/Disable internal preview/(repeating request). Extensions expect
     // that preview/(repeating request) is enabled and active at any point in time.
     // In case the client doesn't explicitly enable repeating requests, the framework
@@ -135,8 +137,9 @@
             @NonNull ExtensionSessionConfiguration config,
             int sessionId)
             throws CameraAccessException, RemoteException {
-        long clientId = CameraExtensionCharacteristics.registerClient(ctx);
-        if (clientId < 0) {
+        final IBinder token = new Binder(TAG + " : " + sessionId);
+        boolean success = CameraExtensionCharacteristics.registerClient(ctx, token);
+        if (!success) {
             throw new UnsupportedOperationException("Unsupported extension!");
         }
 
@@ -224,15 +227,16 @@
         }
 
         extenders.first.init(cameraId, characteristicsMap.get(cameraId).getNativeMetadata());
-        extenders.first.onInit(cameraId, characteristicsMap.get(cameraId).getNativeMetadata());
+        extenders.first.onInit(token, cameraId,
+                characteristicsMap.get(cameraId).getNativeMetadata());
         extenders.second.init(cameraId, characteristicsMap.get(cameraId).getNativeMetadata());
-        extenders.second.onInit(cameraId, characteristicsMap.get(cameraId).getNativeMetadata());
+        extenders.second.onInit(token, cameraId,
+                characteristicsMap.get(cameraId).getNativeMetadata());
 
         CameraExtensionSessionImpl session = new CameraExtensionSessionImpl(
                 extenders.second,
                 extenders.first,
                 supportedPreviewSizes,
-                clientId,
                 cameraDevice,
                 repeatingRequestSurface,
                 burstCaptureSurface,
@@ -240,6 +244,7 @@
                 config.getStateCallback(),
                 config.getExecutor(),
                 sessionId,
+                token,
                 extensionChars.getAvailableCaptureRequestKeys(config.getExtension()),
                 extensionChars.getAvailableCaptureResultKeys(config.getExtension()));
 
@@ -254,7 +259,6 @@
     public CameraExtensionSessionImpl(@NonNull IImageCaptureExtenderImpl imageExtender,
             @NonNull IPreviewExtenderImpl previewExtender,
             @NonNull List<Size> previewSizes,
-            long extensionClientId,
             @NonNull android.hardware.camera2.impl.CameraDeviceImpl cameraDevice,
             @Nullable Surface repeatingRequestSurface,
             @Nullable Surface burstCaptureSurface,
@@ -262,9 +266,9 @@
             @NonNull StateCallback callback,
             @NonNull Executor executor,
             int sessionId,
+            @NonNull IBinder token,
             @NonNull Set<CaptureRequest.Key> requestKeys,
             @Nullable Set<CaptureResult.Key> resultKeys) {
-        mExtensionClientId = extensionClientId;
         mImageExtender = imageExtender;
         mPreviewExtender = previewExtender;
         mCameraDevice = cameraDevice;
@@ -278,8 +282,10 @@
         mHandlerThread.start();
         mHandler = new Handler(mHandlerThread.getLooper());
         mInitialized = false;
+        mSessionClosed = false;
         mInitializeHandler = new InitializeSessionHandler();
         mSessionId = sessionId;
+        mToken = token;
         mSupportedRequestKeys = requestKeys;
         mSupportedResultKeys = resultKeys;
         mCaptureResultsSupported = !resultKeys.isEmpty();
@@ -775,7 +781,12 @@
         synchronized (mInterfaceLock) {
             if (mInitialized) {
                 mInternalRepeatingRequestEnabled = false;
-                mCaptureSession.stopRepeating();
+                try {
+                    mCaptureSession.stopRepeating();
+                } catch (IllegalStateException e) {
+                    // OK: already be closed, nothing else to do
+                    mSessionClosed = true;
+                }
 
                 ArrayList<CaptureStageImpl> captureStageList = new ArrayList<>();
                 try {
@@ -793,13 +804,14 @@
                     Log.e(TAG, "Failed to disable extension! Extension service does not "
                             + "respond!");
                 }
-                if (!captureStageList.isEmpty()) {
+                if (!captureStageList.isEmpty() && !mSessionClosed) {
                     CaptureRequest disableRequest = createRequest(mCameraDevice, captureStageList,
                             mCameraRepeatingSurface, CameraDevice.TEMPLATE_PREVIEW);
                     mCaptureSession.capture(disableRequest,
                             new CloseRequestHandler(mRepeatingRequestImageCallback), mHandler);
                 }
 
+                mSessionClosed = true;
                 mStatsAggregator.commit(/*isFinal*/true); // Commit stats before closing session
                 mCaptureSession.close();
             }
@@ -854,19 +866,22 @@
             mHandlerThread.quit();
 
             try {
-                mPreviewExtender.onDeInit();
-                mImageExtender.onDeInit();
+                if (!mSessionClosed) {
+                    // return value is omitted. nothing can do after session is closed.
+                    mPreviewExtender.onDisableSession();
+                    mImageExtender.onDisableSession();
+                }
+                mPreviewExtender.onDeInit(mToken);
+                mImageExtender.onDeInit(mToken);
             } catch (RemoteException e) {
                 Log.e(TAG, "Failed to release extensions! Extension service does not"
                         + " respond!");
             }
 
-            if (mExtensionClientId >= 0) {
-                CameraExtensionCharacteristics.unregisterClient(mExtensionClientId);
-                if (mInitialized || (mCaptureSession != null)) {
-                    notifyClose = true;
-                    CameraExtensionCharacteristics.releaseSession();
-                }
+            CameraExtensionCharacteristics.unregisterClient(mToken);
+            if (mInitialized || (mCaptureSession != null)) {
+                notifyClose = true;
+                CameraExtensionCharacteristics.releaseSession();
             }
             mInitialized = false;
 
diff --git a/core/java/android/hardware/display/DisplayManager.java b/core/java/android/hardware/display/DisplayManager.java
index 76efce5..022f3c4 100644
--- a/core/java/android/hardware/display/DisplayManager.java
+++ b/core/java/android/hardware/display/DisplayManager.java
@@ -1762,6 +1762,24 @@
          * 123,1,critical,0.8,default;123,1,moderate,0.6,id_2;456,2,moderate,0.9,critical,0.7
          */
         String KEY_BRIGHTNESS_THROTTLING_DATA = "brightness_throttling_data";
+
+        /**
+         * Key for new power controller feature flag. If enabled new DisplayPowerController will
+         * be used.
+         * Read value via {@link android.provider.DeviceConfig#getBoolean(String, String, boolean)}
+         * with {@link android.provider.DeviceConfig#NAMESPACE_DISPLAY_MANAGER} as the namespace.
+         * @hide
+         */
+        String KEY_NEW_POWER_CONTROLLER = "use_newly_structured_display_power_controller";
+
+        /**
+         * Key for normal brightness mode controller feature flag.
+         * It enables NormalBrightnessModeController.
+         * Read value via {@link android.provider.DeviceConfig#getBoolean(String, String, boolean)}
+         * with {@link android.provider.DeviceConfig#NAMESPACE_DISPLAY_MANAGER} as the namespace.
+         * @hide
+         */
+        String KEY_USE_NORMAL_BRIGHTNESS_MODE_CONTROLLER = "use_normal_brightness_mode_controller";
     }
 
     /**
diff --git a/core/java/android/hardware/face/IFaceService.aidl b/core/java/android/hardware/face/IFaceService.aidl
index 9d5073e..7080133 100644
--- a/core/java/android/hardware/face/IFaceService.aidl
+++ b/core/java/android/hardware/face/IFaceService.aidl
@@ -177,5 +177,5 @@
     // Internal operation used to clear face biometric scheduler.
     // Ensures that the scheduler is not stuck.
     @EnforcePermission("USE_BIOMETRIC_INTERNAL")
-    void scheduleWatchdog();
+    oneway void scheduleWatchdog();
 }
diff --git a/core/java/android/hardware/fingerprint/IFingerprintService.aidl b/core/java/android/hardware/fingerprint/IFingerprintService.aidl
index 9975852..e2840ec 100644
--- a/core/java/android/hardware/fingerprint/IFingerprintService.aidl
+++ b/core/java/android/hardware/fingerprint/IFingerprintService.aidl
@@ -213,5 +213,5 @@
     // Internal operation used to clear fingerprint biometric scheduler.
     // Ensures that the scheduler is not stuck.
     @EnforcePermission("USE_BIOMETRIC_INTERNAL")
-    void scheduleWatchdog();
+    oneway void scheduleWatchdog();
 }
diff --git a/core/java/android/os/GraphicsEnvironment.java b/core/java/android/os/GraphicsEnvironment.java
index e6bdfe1..7664bad 100644
--- a/core/java/android/os/GraphicsEnvironment.java
+++ b/core/java/android/os/GraphicsEnvironment.java
@@ -112,9 +112,17 @@
     private static final int ANGLE_GL_DRIVER_ALL_ANGLE_OFF = 0;
 
     // Values for ANGLE_GL_DRIVER_SELECTION_VALUES
-    private static final String ANGLE_GL_DRIVER_CHOICE_DEFAULT = "default";
-    private static final String ANGLE_GL_DRIVER_CHOICE_ANGLE = "angle";
-    private static final String ANGLE_GL_DRIVER_CHOICE_NATIVE = "native";
+    private enum AngleDriverChoice {
+        DEFAULT("default"),
+        ANGLE("angle"),
+        NATIVE("native");
+
+        public final String choice;
+
+        AngleDriverChoice(String choice) {
+            this.choice = choice;
+        }
+    }
 
     private static final String PROPERTY_RO_ANGLE_SUPPORTED = "ro.gfx.angle.supported";
 
@@ -195,15 +203,16 @@
     }
 
     /**
-     * Query to determine if ANGLE should be used
+     * Query to determine the ANGLE driver choice.
      */
-    private boolean shouldUseAngle(Context context, Bundle coreSettings, String packageName) {
+    private AngleDriverChoice queryAngleChoice(Context context, Bundle coreSettings,
+                                               String packageName) {
         if (TextUtils.isEmpty(packageName)) {
             Log.v(TAG, "No package name specified; use the system driver");
-            return false;
+            return AngleDriverChoice.DEFAULT;
         }
 
-        return shouldUseAngleInternal(context, coreSettings, packageName);
+        return queryAngleChoiceInternal(context, coreSettings, packageName);
     }
 
     private int getVulkanVersion(PackageManager pm) {
@@ -424,10 +433,11 @@
      *    forces a choice;
      * 3) Use ANGLE if isAngleEnabledByGameMode() returns true;
      */
-    private boolean shouldUseAngleInternal(Context context, Bundle bundle, String packageName) {
+    private AngleDriverChoice queryAngleChoiceInternal(Context context, Bundle bundle,
+                                                       String packageName) {
         // Make sure we have a good package name
         if (TextUtils.isEmpty(packageName)) {
-            return false;
+            return AngleDriverChoice.DEFAULT;
         }
 
         // Check the semi-global switch (i.e. once system has booted enough) for whether ANGLE
@@ -442,7 +452,7 @@
         }
         if (allUseAngle == ANGLE_GL_DRIVER_ALL_ANGLE_ON) {
             Log.v(TAG, "Turn on ANGLE for all applications.");
-            return true;
+            return AngleDriverChoice.ANGLE;
         }
 
         // Get the per-application settings lists
@@ -465,7 +475,7 @@
                             + optInPackages.size() + ", "
                         + "number of values: "
                             + optInValues.size());
-            return mEnabledByGameMode;
+            return mEnabledByGameMode ? AngleDriverChoice.ANGLE : AngleDriverChoice.DEFAULT;
         }
 
         // See if this application is listed in the per-application settings list
@@ -473,7 +483,7 @@
 
         if (pkgIndex < 0) {
             Log.v(TAG, packageName + " is not listed in per-application setting");
-            return mEnabledByGameMode;
+            return mEnabledByGameMode ? AngleDriverChoice.ANGLE : AngleDriverChoice.DEFAULT;
         }
         mAngleOptInIndex = pkgIndex;
 
@@ -483,14 +493,14 @@
         Log.v(TAG,
                 "ANGLE Developer option for '" + packageName + "' "
                         + "set to: '" + optInValue + "'");
-        if (optInValue.equals(ANGLE_GL_DRIVER_CHOICE_ANGLE)) {
-            return true;
-        } else if (optInValue.equals(ANGLE_GL_DRIVER_CHOICE_NATIVE)) {
-            return false;
+        if (optInValue.equals(AngleDriverChoice.ANGLE.choice)) {
+            return AngleDriverChoice.ANGLE;
+        } else if (optInValue.equals(AngleDriverChoice.NATIVE.choice)) {
+            return AngleDriverChoice.NATIVE;
         } else {
             // The user either chose default or an invalid value; go with the default driver or what
             // the game mode indicates
-            return mEnabledByGameMode;
+            return mEnabledByGameMode ? AngleDriverChoice.ANGLE : AngleDriverChoice.DEFAULT;
         }
     }
 
@@ -558,7 +568,13 @@
     private boolean setupAngle(Context context, Bundle bundle, PackageManager packageManager,
             String packageName) {
 
-        if (!shouldUseAngle(context, bundle, packageName)) {
+        final AngleDriverChoice angleDriverChoice = queryAngleChoice(context, bundle, packageName);
+        if (angleDriverChoice == AngleDriverChoice.DEFAULT) {
+            return false;
+        }
+
+        if (queryAngleChoice(context, bundle, packageName) == AngleDriverChoice.NATIVE) {
+            nativeSetAngleInfo("", true, packageName, null);
             return false;
         }
 
@@ -627,10 +643,10 @@
             Log.d(TAG, "ANGLE package libs: " + paths);
         }
 
-        // If we make it to here, ANGLE will be used.  Call setAngleInfo() with the package name,
-        // and features to use.
+        // If we make it to here, ANGLE apk will be used.  Call nativeSetAngleInfo() with the
+        // application package name and ANGLE features to use.
         final String[] features = getAngleEglFeatures(context, bundle);
-        setAngleInfo(paths, false, packageName, features);
+        nativeSetAngleInfo(paths, false, packageName, features);
 
         return true;
     }
@@ -652,10 +668,10 @@
             return false;
         }
 
-        // If we make it to here, ANGLE will be used.  Call setAngleInfo() with the package name,
-        // and features to use.
+        // If we make it to here, system ANGLE will be used.  Call nativeSetAngleInfo() with
+        // the application package name and ANGLE features to use.
         final String[] features = getAngleEglFeatures(context, bundle);
-        setAngleInfo("", true, packageName, features);
+        nativeSetAngleInfo("system", false, packageName, features);
         return true;
     }
 
@@ -936,8 +952,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, boolean useSystemAngle, String packageName,
-            String[] features);
+    private static native void nativeSetAngleInfo(String path, boolean useNativeDriver,
+            String packageName, String[] features);
     private static native boolean setInjectLayersPrSetDumpable();
     private static native void nativeToggleAngleAsSystemDriver(boolean enabled);
 
diff --git a/core/java/android/os/TEST_MAPPING b/core/java/android/os/TEST_MAPPING
index cc54266..5c4aa4a 100644
--- a/core/java/android/os/TEST_MAPPING
+++ b/core/java/android/os/TEST_MAPPING
@@ -52,8 +52,20 @@
       ],
       "name": "FrameworksServicesTests",
       "options": [
-        { "include-filter": "com.android.server.am.BatteryStatsServiceTest" },
-        { "include-filter": "com.android.server.power.stats.BatteryStatsTests" }
+        { "include-filter": "com.android.server.am.BatteryStatsServiceTest" }
+      ]
+    },
+    {
+      "file_patterns": [
+        "BatteryStats[^/]*\\.java",
+        "BatteryUsageStats[^/]*\\.java",
+        "PowerComponents\\.java",
+        "[^/]*BatteryConsumer[^/]*\\.java"
+      ],
+      "name": "FrameworksServicesTests",
+      "options": [
+        { "include-filter": "com.android.server.power.stats" },
+        { "exclude-filter": "com.android.server.power.stats.BatteryStatsTests" }
       ]
     },
     {
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index d425bf8..820f454 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -12613,6 +12613,26 @@
         public static final String MOBILE_DATA_ALWAYS_ON = "mobile_data_always_on";
 
         /**
+         * The duration in milliseconds of each action, separated by commas. Ex:
+         *
+         * "18000,18000,18000,18000,0"
+         *
+         * See com.android.internal.telephony.data.DataStallRecoveryManager for more info
+         * @hide
+         */
+        public static final String DSRM_DURATION_MILLIS = "dsrm_duration_millis";
+
+        /**
+         * The list of DSRM enabled actions, separated by commas. Ex:
+         *
+         * "true,true,false,true,true"
+         *
+         * See com.android.internal.telephony.data.DataStallRecoveryManager for more info
+         * @hide
+         */
+        public static final String DSRM_ENABLED_ACTIONS = "dsrm_enabled_actions";
+
+        /**
          * Whether the wifi data connection should remain active even when higher
          * priority networks like Ethernet are active, to keep both networks.
          * In the case where higher priority networks are connected, wifi will be
diff --git a/core/java/android/service/dreams/DreamService.java b/core/java/android/service/dreams/DreamService.java
index 5e7f5d6..9b19937 100644
--- a/core/java/android/service/dreams/DreamService.java
+++ b/core/java/android/service/dreams/DreamService.java
@@ -26,7 +26,6 @@
 import android.annotation.SdkConstant.SdkConstantType;
 import android.annotation.TestApi;
 import android.app.Activity;
-import android.app.ActivityTaskManager;
 import android.app.AlarmManager;
 import android.app.Service;
 import android.compat.annotation.UnsupportedAppUsage;
@@ -1268,9 +1267,7 @@
                     fetchDreamLabel(this, serviceInfo, isPreviewMode));
 
             try {
-                if (!ActivityTaskManager.getService().startDreamActivity(i)) {
-                    detach();
-                }
+                mDreamManager.startDreamActivity(i);
             } catch (SecurityException e) {
                 Log.w(mTag,
                         "Received SecurityException trying to start DreamActivity. "
diff --git a/core/java/android/service/dreams/IDreamManager.aidl b/core/java/android/service/dreams/IDreamManager.aidl
index 609425c..dd8b3de 100644
--- a/core/java/android/service/dreams/IDreamManager.aidl
+++ b/core/java/android/service/dreams/IDreamManager.aidl
@@ -17,6 +17,7 @@
 package android.service.dreams;
 
 import android.content.ComponentName;
+import android.content.Intent;
 import android.os.Bundle;
 import android.os.ParcelFileDescriptor;
 import android.os.IBinder;
@@ -45,4 +46,5 @@
     void setDreamComponentsForUser(int userId, in ComponentName[] componentNames);
     void setSystemDreamComponent(in ComponentName componentName);
     void registerDreamOverlayService(in ComponentName componentName);
+    void startDreamActivity(in Intent intent);
 }
diff --git a/core/java/android/text/StaticLayout.java b/core/java/android/text/StaticLayout.java
index 6371da4..ab9cff0 100644
--- a/core/java/android/text/StaticLayout.java
+++ b/core/java/android/text/StaticLayout.java
@@ -25,7 +25,6 @@
 import android.graphics.text.LineBreakConfig;
 import android.graphics.text.LineBreaker;
 import android.os.Build;
-import android.os.SystemProperties;
 import android.text.style.LeadingMarginSpan;
 import android.text.style.LeadingMarginSpan.LeadingMarginSpan2;
 import android.text.style.LineHeightSpan;
@@ -33,7 +32,6 @@
 import android.util.Log;
 import android.util.Pools.SynchronizedPool;
 
-import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.ArrayUtils;
 import com.android.internal.util.GrowingArrayUtils;
 
@@ -75,13 +73,6 @@
      * default values.
      */
     public final static class Builder {
-        // The content length threshold to enable LINE_BREAK_WORD_STYLE_PHRASE.
-        private static final int DEFAULT_LINECOUNT_THRESHOLD_FOR_PHRASE = 3;
-
-        // The property of content length threshold to enable LINE_BREAK_WORD_STYLE_PHRASE.
-        private static final String PROPERTY_LINECOUNT_THRESHOLD_FOR_PHRASE =
-                "android.phrase.linecount.threshold";
-
         private Builder() {}
 
         /**
@@ -440,55 +431,11 @@
          */
         @NonNull
         public StaticLayout build() {
-            reviseLineBreakConfig();
             StaticLayout result = new StaticLayout(this);
             Builder.recycle(this);
             return result;
         }
 
-        private void reviseLineBreakConfig() {
-            boolean autoPhraseBreaking = mLineBreakConfig.getAutoPhraseBreaking();
-            int wordStyle = mLineBreakConfig.getLineBreakWordStyle();
-            if (autoPhraseBreaking) {
-                if (wordStyle != LineBreakConfig.LINE_BREAK_WORD_STYLE_PHRASE) {
-                    if (shouldEnablePhraseBreaking()) {
-                        mLineBreakConfig = LineBreakConfig.getLineBreakConfig(
-                                mLineBreakConfig.getLineBreakStyle(),
-                                LineBreakConfig.LINE_BREAK_WORD_STYLE_PHRASE,
-                                mLineBreakConfig.getAutoPhraseBreaking());
-                    }
-                }
-            }
-        }
-
-        private boolean shouldEnablePhraseBreaking() {
-            if (TextUtils.isEmpty(mText) || mWidth <= 0) {
-                return false;
-            }
-            int lineLimit = SystemProperties.getInt(
-                    PROPERTY_LINECOUNT_THRESHOLD_FOR_PHRASE,
-                    DEFAULT_LINECOUNT_THRESHOLD_FOR_PHRASE);
-            double desiredWidth = (double) Layout.getDesiredWidth(mText, mStart,
-                    mEnd, mPaint, mTextDir);
-            int lineCount = (int) Math.ceil(desiredWidth / mWidth);
-            if (lineCount > 0 && lineCount <= lineLimit) {
-                return true;
-            }
-            return false;
-        }
-
-        /**
-         * Get the line break word style.
-         *
-         * @return The current line break word style.
-         *
-         * @hide
-         */
-        @VisibleForTesting
-        public int getLineBreakWordStyle() {
-            return mLineBreakConfig.getLineBreakWordStyle();
-        }
-
         private CharSequence mText;
         private int mStart;
         private int mEnd;
diff --git a/core/java/android/util/FeatureFlagUtils.java b/core/java/android/util/FeatureFlagUtils.java
index ff7d8bb..0071a0d 100644
--- a/core/java/android/util/FeatureFlagUtils.java
+++ b/core/java/android/util/FeatureFlagUtils.java
@@ -86,9 +86,6 @@
     public static final String SETTINGS_NEED_CONNECTED_BLE_DEVICE_FOR_BROADCAST =
             "settings_need_connected_ble_device_for_broadcast";
 
-    /** @hide */
-    public static final String SETTINGS_AUTO_TEXT_WRAPPING = "settings_auto_text_wrapping";
-
     /**
      * Enable new language and keyboard settings UI
      * @hide
@@ -225,7 +222,6 @@
         DEFAULT_FLAGS.put(SETTINGS_ENABLE_MONITOR_PHANTOM_PROCS, "true");
         DEFAULT_FLAGS.put(SETTINGS_APP_ALLOW_DARK_THEME_ACTIVATION_AT_BEDTIME, "true");
         DEFAULT_FLAGS.put(SETTINGS_NEED_CONNECTED_BLE_DEVICE_FOR_BROADCAST, "true");
-        DEFAULT_FLAGS.put(SETTINGS_AUTO_TEXT_WRAPPING, "false");
         DEFAULT_FLAGS.put(SETTINGS_NEW_KEYBOARD_UI, "true");
         DEFAULT_FLAGS.put(SETTINGS_NEW_KEYBOARD_MODIFIER_KEY, "true");
         DEFAULT_FLAGS.put(SETTINGS_NEW_KEYBOARD_TRACKPAD, "true");
@@ -253,7 +249,6 @@
         PERSISTENT_FLAGS.add(SETTINGS_SUPPORT_LARGE_SCREEN);
         PERSISTENT_FLAGS.add(SETTINGS_ENABLE_MONITOR_PHANTOM_PROCS);
         PERSISTENT_FLAGS.add(SETTINGS_APP_ALLOW_DARK_THEME_ACTIVATION_AT_BEDTIME);
-        PERSISTENT_FLAGS.add(SETTINGS_AUTO_TEXT_WRAPPING);
         PERSISTENT_FLAGS.add(SETTINGS_NEW_KEYBOARD_UI);
         PERSISTENT_FLAGS.add(SETTINGS_NEW_KEYBOARD_MODIFIER_KEY);
         PERSISTENT_FLAGS.add(SETTINGS_NEW_KEYBOARD_TRACKPAD);
diff --git a/core/java/android/view/InsetsController.java b/core/java/android/view/InsetsController.java
index 5019b85..c1eacb5 100644
--- a/core/java/android/view/InsetsController.java
+++ b/core/java/android/view/InsetsController.java
@@ -797,7 +797,7 @@
             }
 
             WindowInsets insets = state.calculateInsets(mFrame, mState /* ignoringVisibilityState*/,
-                    mLastInsets.isRound(), mLastInsets.shouldAlwaysConsumeSystemBars(),
+                    mLastInsets.isRound(), false /* alwaysConsumeSystemBars */,
                     mLastLegacySoftInputMode, mLastLegacyWindowFlags, mLastLegacySystemUiFlags,
                     mWindowType, mLastWindowingMode, null /* idSideMap */);
             mHost.dispatchWindowInsetsAnimationProgress(insets,
@@ -841,7 +841,6 @@
         return mLastDispatchedState;
     }
 
-    @VisibleForTesting
     public boolean onStateChanged(InsetsState state) {
         boolean stateChanged = false;
         if (!CAPTION_ON_SHELL) {
diff --git a/core/java/android/view/InsetsSource.java b/core/java/android/view/InsetsSource.java
index e101849..6441186 100644
--- a/core/java/android/view/InsetsSource.java
+++ b/core/java/android/view/InsetsSource.java
@@ -68,10 +68,16 @@
      */
     public static final int FLAG_INSETS_ROUNDED_CORNER = 1 << 1;
 
+    /**
+     * Controls whether the insets provided by this source should be forcibly consumed.
+     */
+    public static final int FLAG_FORCE_CONSUMING = 1 << 2;
+
     @Retention(RetentionPolicy.SOURCE)
     @IntDef(flag = true, prefix = "FLAG_", value = {
             FLAG_SUPPRESS_SCRIM,
             FLAG_INSETS_ROUNDED_CORNER,
+            FLAG_FORCE_CONSUMING,
     })
     public @interface Flags {}
 
@@ -328,6 +334,9 @@
         if ((flags & FLAG_INSETS_ROUNDED_CORNER) != 0) {
             joiner.add("INSETS_ROUNDED_CORNER");
         }
+        if ((flags & FLAG_FORCE_CONSUMING) != 0) {
+            joiner.add("FORCE_CONSUMING");
+        }
         return joiner.toString();
     }
 
diff --git a/core/java/android/view/InsetsState.java b/core/java/android/view/InsetsState.java
index dceae90..c13b9ab 100644
--- a/core/java/android/view/InsetsState.java
+++ b/core/java/android/view/InsetsState.java
@@ -16,6 +16,7 @@
 
 package android.view;
 
+import static android.view.InsetsSource.FLAG_FORCE_CONSUMING;
 import static android.view.InsetsSource.FLAG_INSETS_ROUNDED_CORNER;
 import static android.view.InsetsStateProto.DISPLAY_CUTOUT;
 import static android.view.InsetsStateProto.DISPLAY_FRAME;
@@ -144,12 +145,18 @@
         boolean[] typeVisibilityMap = new boolean[Type.SIZE];
         final Rect relativeFrame = new Rect(frame);
         final Rect relativeFrameMax = new Rect(frame);
+        @InsetsType int forceConsumingTypes = 0;
         @InsetsType int suppressScrimTypes = 0;
         for (int i = mSources.size() - 1; i >= 0; i--) {
             final InsetsSource source = mSources.valueAt(i);
+            final @InsetsType int type = source.getType();
+
+            if ((source.getFlags() & InsetsSource.FLAG_FORCE_CONSUMING) != 0) {
+                forceConsumingTypes |= type;
+            }
 
             if ((source.getFlags() & InsetsSource.FLAG_SUPPRESS_SCRIM) != 0) {
-                suppressScrimTypes |= source.getType();
+                suppressScrimTypes |= type;
             }
 
             processSource(source, relativeFrame, false /* ignoreVisibility */, typeInsetsMap,
@@ -157,7 +164,7 @@
 
             // IME won't be reported in max insets as the size depends on the EditorInfo of the IME
             // target.
-            if (source.getType() != WindowInsets.Type.ime()) {
+            if (type != WindowInsets.Type.ime()) {
                 InsetsSource ignoringVisibilitySource = ignoringVisibilityState != null
                         ? ignoringVisibilityState.peekSource(source.getId())
                         : source;
@@ -178,13 +185,13 @@
         if ((legacyWindowFlags & FLAG_FULLSCREEN) != 0) {
             compatInsetsTypes &= ~statusBars();
         }
-        if (clearsCompatInsets(windowType, legacyWindowFlags, windowingMode)
-                && !alwaysConsumeSystemBars) {
-            compatInsetsTypes = 0;
+        if (clearsCompatInsets(windowType, legacyWindowFlags, windowingMode)) {
+            // Clear all types but forceConsumingTypes.
+            compatInsetsTypes &= forceConsumingTypes;
         }
 
         return new WindowInsets(typeInsetsMap, typeMaxInsetsMap, typeVisibilityMap, isScreenRound,
-                alwaysConsumeSystemBars, suppressScrimTypes, calculateRelativeCutout(frame),
+                forceConsumingTypes, suppressScrimTypes, calculateRelativeCutout(frame),
                 calculateRelativeRoundedCorners(frame),
                 calculateRelativePrivacyIndicatorBounds(frame),
                 calculateRelativeDisplayShape(frame),
@@ -290,9 +297,8 @@
 
     public Insets calculateVisibleInsets(Rect frame, int windowType, int windowingMode,
             @SoftInputModeFlags int softInputMode, int windowFlags) {
-        if (clearsCompatInsets(windowType, windowFlags, windowingMode)) {
-            return Insets.NONE;
-        }
+        final boolean clearsCompatInsets = clearsCompatInsets(
+                windowType, windowFlags, windowingMode);
         final int softInputAdjustMode = softInputMode & SOFT_INPUT_MASK_ADJUST;
         final int visibleInsetsTypes = softInputAdjustMode != SOFT_INPUT_ADJUST_NOTHING
                 ? systemBars() | ime()
@@ -303,6 +309,9 @@
             if ((source.getType() & visibleInsetsTypes) == 0) {
                 continue;
             }
+            if (clearsCompatInsets && !source.hasFlags(FLAG_FORCE_CONSUMING)) {
+                continue;
+            }
             insets = Insets.max(source.calculateVisibleInsets(frame), insets);
         }
         return insets;
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 9316dbf..bef28b2 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -3855,7 +3855,15 @@
         mWmsRequestSyncGroupState = WMS_SYNC_PENDING;
         mWmsRequestSyncGroup = new SurfaceSyncGroup("wmsSync-" + mTag, t -> {
             mWmsRequestSyncGroupState = WMS_SYNC_MERGED;
-            reportDrawFinished(t, seqId);
+            // See b/286355097. If the current process is not system, then invoking finishDraw on
+            // any thread is fine since once it calls into system process, finishDrawing will run
+            // on a different thread. However, when the current process is system, the finishDraw in
+            // system server will be run on the current thread, which could result in a deadlock.
+            if (mWindowSession instanceof Binder) {
+                reportDrawFinished(t, seqId);
+            } else {
+                mHandler.postAtFrontOfQueue(() -> reportDrawFinished(t, seqId));
+            }
         });
         if (DEBUG_BLAST) {
             Log.d(mTag, "Setup new sync=" + mWmsRequestSyncGroup.getName());
diff --git a/core/java/android/view/WindowInsets.java b/core/java/android/view/WindowInsets.java
index 4acaea8..57a4161 100644
--- a/core/java/android/view/WindowInsets.java
+++ b/core/java/android/view/WindowInsets.java
@@ -84,13 +84,7 @@
     @Nullable private final PrivacyIndicatorBounds mPrivacyIndicatorBounds;
     @Nullable private final DisplayShape mDisplayShape;
 
-    /**
-     * In multi-window we force show the navigation bar. Because we don't want that the surface size
-     * changes in this mode, we instead have a flag whether the navigation bar size should always
-     * be consumed, so the app is treated like there is no virtual navigation bar at all.
-     */
-    private final boolean mAlwaysConsumeSystemBars;
-
+    private final @InsetsType int mForceConsumingTypes;
     private final @InsetsType int mSuppressScrimTypes;
     private final boolean mSystemWindowInsetsConsumed;
     private final boolean mStableInsetsConsumed;
@@ -117,7 +111,7 @@
 
     static {
         CONSUMED = new WindowInsets(createCompatTypeMap(null), createCompatTypeMap(null),
-                createCompatVisibilityMap(createCompatTypeMap(null)), false, false, 0, null,
+                createCompatVisibilityMap(createCompatTypeMap(null)), false, 0, 0, null,
                 null, null, null, systemBars(), false);
     }
 
@@ -137,7 +131,8 @@
             @Nullable Insets[] typeMaxInsetsMap,
             boolean[] typeVisibilityMap,
             boolean isRound,
-            boolean alwaysConsumeSystemBars, @InsetsType int suppressScrimTypes,
+            @InsetsType int forceConsumingTypes,
+            @InsetsType int suppressScrimTypes,
             DisplayCutout displayCutout,
             RoundedCorners roundedCorners,
             PrivacyIndicatorBounds privacyIndicatorBounds,
@@ -155,7 +150,7 @@
 
         mTypeVisibilityMap = typeVisibilityMap;
         mIsRound = isRound;
-        mAlwaysConsumeSystemBars = alwaysConsumeSystemBars;
+        mForceConsumingTypes = forceConsumingTypes;
         mSuppressScrimTypes = suppressScrimTypes;
         mCompatInsetsTypes = compatInsetsTypes;
         mCompatIgnoreVisibility = compatIgnoreVisibility;
@@ -178,7 +173,7 @@
         this(src.mSystemWindowInsetsConsumed ? null : src.mTypeInsetsMap,
                 src.mStableInsetsConsumed ? null : src.mTypeMaxInsetsMap,
                 src.mTypeVisibilityMap, src.mIsRound,
-                src.mAlwaysConsumeSystemBars, src.mSuppressScrimTypes,
+                src.mForceConsumingTypes, src.mSuppressScrimTypes,
                 displayCutoutCopyConstructorArgument(src),
                 src.mRoundedCorners,
                 src.mPrivacyIndicatorBounds,
@@ -235,7 +230,7 @@
     /** @hide */
     @UnsupportedAppUsage
     public WindowInsets(Rect systemWindowInsets) {
-        this(createCompatTypeMap(systemWindowInsets), null, new boolean[SIZE], false, false, 0,
+        this(createCompatTypeMap(systemWindowInsets), null, new boolean[SIZE], false, 0, 0,
                 null, null, null, null, systemBars(), false /* compatIgnoreVisibility */);
     }
 
@@ -556,7 +551,7 @@
         return new WindowInsets(mSystemWindowInsetsConsumed ? null : mTypeInsetsMap,
                 mStableInsetsConsumed ? null : mTypeMaxInsetsMap,
                 mTypeVisibilityMap,
-                mIsRound, mAlwaysConsumeSystemBars, mSuppressScrimTypes,
+                mIsRound, mForceConsumingTypes, mSuppressScrimTypes,
                 null /* displayCutout */, mRoundedCorners, mPrivacyIndicatorBounds, mDisplayShape,
                 mCompatInsetsTypes, mCompatIgnoreVisibility);
     }
@@ -607,7 +602,7 @@
     public WindowInsets consumeSystemWindowInsets() {
         return new WindowInsets(null, null,
                 mTypeVisibilityMap,
-                mIsRound, mAlwaysConsumeSystemBars, mSuppressScrimTypes,
+                mIsRound, mForceConsumingTypes, mSuppressScrimTypes,
                 // If the system window insets types contain displayCutout, we should also consume
                 // it.
                 (mCompatInsetsTypes & displayCutout()) != 0
@@ -895,8 +890,8 @@
     /**
      * @hide
      */
-    public boolean shouldAlwaysConsumeSystemBars() {
-        return mAlwaysConsumeSystemBars;
+    public @InsetsType int getForceConsumingTypes() {
+        return mForceConsumingTypes;
     }
 
     /**
@@ -930,6 +925,8 @@
         result.append("\n    ");
         result.append(mDisplayShape != null ? "displayShape=" + mDisplayShape : "");
         result.append("\n    ");
+        result.append("forceConsumingTypes=" + Type.toString(mForceConsumingTypes));
+        result.append("\n    ");
         result.append("suppressScrimTypes=" + Type.toString(mSuppressScrimTypes));
         result.append("\n    ");
         result.append("compatInsetsTypes=" + Type.toString(mCompatInsetsTypes));
@@ -1027,7 +1024,7 @@
                         ? null
                         : insetInsets(mTypeMaxInsetsMap, left, top, right, bottom),
                 mTypeVisibilityMap,
-                mIsRound, mAlwaysConsumeSystemBars, mSuppressScrimTypes,
+                mIsRound, mForceConsumingTypes, mSuppressScrimTypes,
                 mDisplayCutoutConsumed
                         ? null
                         : mDisplayCutout == null
@@ -1050,7 +1047,7 @@
         WindowInsets that = (WindowInsets) o;
 
         return mIsRound == that.mIsRound
-                && mAlwaysConsumeSystemBars == that.mAlwaysConsumeSystemBars
+                && mForceConsumingTypes == that.mForceConsumingTypes
                 && mSuppressScrimTypes == that.mSuppressScrimTypes
                 && mSystemWindowInsetsConsumed == that.mSystemWindowInsetsConsumed
                 && mStableInsetsConsumed == that.mStableInsetsConsumed
@@ -1068,7 +1065,7 @@
     public int hashCode() {
         return Objects.hash(Arrays.hashCode(mTypeInsetsMap), Arrays.hashCode(mTypeMaxInsetsMap),
                 Arrays.hashCode(mTypeVisibilityMap), mIsRound, mDisplayCutout, mRoundedCorners,
-                mAlwaysConsumeSystemBars, mSuppressScrimTypes, mSystemWindowInsetsConsumed,
+                mForceConsumingTypes, mSuppressScrimTypes, mSystemWindowInsetsConsumed,
                 mStableInsetsConsumed, mDisplayCutoutConsumed, mPrivacyIndicatorBounds,
                 mDisplayShape);
     }
@@ -1134,7 +1131,7 @@
         private DisplayShape mDisplayShape = DisplayShape.NONE;
 
         private boolean mIsRound;
-        private boolean mAlwaysConsumeSystemBars;
+        private @InsetsType int mForceConsumingTypes;
         private @InsetsType int mSuppressScrimTypes;
 
         private PrivacyIndicatorBounds mPrivacyIndicatorBounds = new PrivacyIndicatorBounds();
@@ -1162,7 +1159,7 @@
             mDisplayCutout = displayCutoutCopyConstructorArgument(insets);
             mRoundedCorners = insets.mRoundedCorners;
             mIsRound = insets.mIsRound;
-            mAlwaysConsumeSystemBars = insets.mAlwaysConsumeSystemBars;
+            mForceConsumingTypes = insets.mForceConsumingTypes;
             mSuppressScrimTypes = insets.mSuppressScrimTypes;
             mPrivacyIndicatorBounds = insets.mPrivacyIndicatorBounds;
             mDisplayShape = insets.mDisplayShape;
@@ -1433,7 +1430,15 @@
         /** @hide */
         @NonNull
         public Builder setAlwaysConsumeSystemBars(boolean alwaysConsumeSystemBars) {
-            mAlwaysConsumeSystemBars = alwaysConsumeSystemBars;
+            // TODO (b/277891341): Remove this and related usages. This has been replaced by
+            //                     #setForceConsumingTypes.
+            return this;
+        }
+
+        /** @hide */
+        @NonNull
+        public Builder setForceConsumingTypes(@InsetsType int forceConsumingTypes) {
+            mForceConsumingTypes = forceConsumingTypes;
             return this;
         }
 
@@ -1453,7 +1458,7 @@
         public WindowInsets build() {
             return new WindowInsets(mSystemInsetsConsumed ? null : mTypeInsetsMap,
                     mStableInsetsConsumed ? null : mTypeMaxInsetsMap, mTypeVisibilityMap,
-                    mIsRound, mAlwaysConsumeSystemBars, mSuppressScrimTypes, mDisplayCutout,
+                    mIsRound, mForceConsumingTypes, mSuppressScrimTypes, mDisplayCutout,
                     mRoundedCorners, mPrivacyIndicatorBounds, mDisplayShape, systemBars(),
                     false /* compatIgnoreVisibility */);
         }
diff --git a/core/java/android/view/accessibility/IWindowMagnificationConnection.aidl b/core/java/android/view/accessibility/IWindowMagnificationConnection.aidl
index 8a30f8c..a11c6d0 100644
--- a/core/java/android/view/accessibility/IWindowMagnificationConnection.aidl
+++ b/core/java/android/view/accessibility/IWindowMagnificationConnection.aidl
@@ -115,4 +115,13 @@
      * @param callback the interface to be called.
      */
     void setConnectionCallback(in IWindowMagnificationConnectionCallback callback);
+
+    /**
+     * Notify System UI the magnification scale on the specified display for userId is changed.
+     *
+     * @param userId the user id.
+     * @param displayId the logical display id.
+     * @param scale magnification scale.
+     */
+    void onUserMagnificationScaleChanged(int userId, int displayId, float scale);
 }
diff --git a/core/java/android/view/inputmethod/RemoteInputConnectionImpl.java b/core/java/android/view/inputmethod/RemoteInputConnectionImpl.java
index aa9225b..e9d7b9b 100644
--- a/core/java/android/view/inputmethod/RemoteInputConnectionImpl.java
+++ b/core/java/android/view/inputmethod/RemoteInputConnectionImpl.java
@@ -1118,7 +1118,7 @@
             @InputConnection.CursorUpdateFilter int cursorUpdateFilter, int imeDisplayId) {
         final InputConnection ic = getInputConnection();
         if (ic == null || !isActive()) {
-            Log.w(TAG, "requestCursorAnchorInfo on inactive InputConnection");
+            Log.w(TAG, "requestCursorUpdates on inactive InputConnection");
             return false;
         }
         if (mParentInputMethodManager.mRequestCursorUpdateDisplayIdCheck.get()
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index 438b974..27d8a8f 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -156,7 +156,6 @@
 import android.util.ArraySet;
 import android.util.AttributeSet;
 import android.util.DisplayMetrics;
-import android.util.FeatureFlagUtils;
 import android.util.IntArray;
 import android.util.Log;
 import android.util.SparseIntArray;
@@ -831,11 +830,6 @@
     private int mLineBreakStyle = DEFAULT_LINE_BREAK_STYLE;
     private int mLineBreakWordStyle = DEFAULT_LINE_BREAK_WORD_STYLE;
 
-    // The auto option for LINE_BREAK_WORD_STYLE_PHRASE may not be applied in recycled view due to
-    // one-way flag flipping. This is a tentative limitation during experiment and will not have the
-    // issue once this is finalized to LINE_BREAK_WORD_STYLE_PHRASE_AUTO option.
-    private boolean mUserSpeficiedLineBreakwordStyle = false;
-
     // This is used to reflect the current user preference for changing font weight and making text
     // more bold.
     private int mFontWeightAdjustment;
@@ -1546,9 +1540,6 @@
                     break;
 
                 case com.android.internal.R.styleable.TextView_lineBreakWordStyle:
-                    if (a.hasValue(attr)) {
-                        mUserSpeficiedLineBreakwordStyle = true;
-                    }
                     mLineBreakWordStyle = a.getInt(attr,
                             LineBreakConfig.LINE_BREAK_WORD_STYLE_NONE);
                     break;
@@ -4350,7 +4341,6 @@
                     break;
                 case com.android.internal.R.styleable.TextAppearance_lineBreakWordStyle:
                     attributes.mHasLineBreakWordStyle = true;
-                    mUserSpeficiedLineBreakwordStyle = true;
                     attributes.mLineBreakWordStyle =
                             appearance.getInt(attr, attributes.mLineBreakWordStyle);
                     break;
@@ -5086,7 +5076,6 @@
      * @param lineBreakWordStyle The line-break word style for the text.
      */
     public void setLineBreakWordStyle(@LineBreakConfig.LineBreakWordStyle int lineBreakWordStyle) {
-        mUserSpeficiedLineBreakwordStyle = true;
         if (mLineBreakWordStyle != lineBreakWordStyle) {
             mLineBreakWordStyle = lineBreakWordStyle;
             if (mLayout != null) {
@@ -5122,12 +5111,8 @@
      * @see PrecomputedText
      */
     public @NonNull PrecomputedText.Params getTextMetricsParams() {
-        final boolean autoPhraseBreaking =
-                !mUserSpeficiedLineBreakwordStyle && FeatureFlagUtils.isEnabled(mContext,
-                        FeatureFlagUtils.SETTINGS_AUTO_TEXT_WRAPPING);
         return new PrecomputedText.Params(new TextPaint(mTextPaint),
-                LineBreakConfig.getLineBreakConfig(mLineBreakStyle, mLineBreakWordStyle,
-                        autoPhraseBreaking),
+                LineBreakConfig.getLineBreakConfig(mLineBreakStyle, mLineBreakWordStyle),
                 getTextDirectionHeuristic(),
                 mBreakStrategy, mHyphenationFrequency);
     }
@@ -5147,7 +5132,6 @@
         LineBreakConfig lineBreakConfig = params.getLineBreakConfig();
         mLineBreakStyle = lineBreakConfig.getLineBreakStyle();
         mLineBreakWordStyle = lineBreakConfig.getLineBreakWordStyle();
-        mUserSpeficiedLineBreakwordStyle = true;
         if (mLayout != null) {
             nullLayouts();
             requestLayout();
@@ -7077,13 +7061,10 @@
             if (mTextDir == null) {
                 mTextDir = getTextDirectionHeuristic();
             }
-            final boolean autoPhraseBreaking =
-                    !mUserSpeficiedLineBreakwordStyle && FeatureFlagUtils.isEnabled(mContext,
-                            FeatureFlagUtils.SETTINGS_AUTO_TEXT_WRAPPING);
             final @PrecomputedText.Params.CheckResultUsableResult int checkResult =
                     precomputed.getParams().checkResultUsable(getPaint(), mTextDir, mBreakStrategy,
                             mHyphenationFrequency, LineBreakConfig.getLineBreakConfig(
-                                    mLineBreakStyle, mLineBreakWordStyle, autoPhraseBreaking));
+                                    mLineBreakStyle, mLineBreakWordStyle));
             switch (checkResult) {
                 case PrecomputedText.Params.UNUSABLE:
                     throw new IllegalArgumentException(
@@ -10640,9 +10621,6 @@
             }
             // TODO: code duplication with makeSingleLayout()
             if (mHintLayout == null) {
-                final boolean autoPhraseBreaking =
-                        !mUserSpeficiedLineBreakwordStyle && FeatureFlagUtils.isEnabled(mContext,
-                                FeatureFlagUtils.SETTINGS_AUTO_TEXT_WRAPPING);
                 StaticLayout.Builder builder = StaticLayout.Builder.obtain(mHint, 0,
                         mHint.length(), mTextPaint, hintWidth)
                         .setAlignment(alignment)
@@ -10655,7 +10633,7 @@
                         .setJustificationMode(mJustificationMode)
                         .setMaxLines(mMaxMode == LINES ? mMaximum : Integer.MAX_VALUE)
                         .setLineBreakConfig(LineBreakConfig.getLineBreakConfig(
-                                mLineBreakStyle, mLineBreakWordStyle, autoPhraseBreaking));
+                                mLineBreakStyle, mLineBreakWordStyle));
                 if (shouldEllipsize) {
                     builder.setEllipsize(mEllipsize)
                             .setEllipsizedWidth(ellipsisWidth);
@@ -10759,9 +10737,6 @@
             }
         }
         if (result == null) {
-            final boolean autoPhraseBreaking =
-                    !mUserSpeficiedLineBreakwordStyle && FeatureFlagUtils.isEnabled(mContext,
-                            FeatureFlagUtils.SETTINGS_AUTO_TEXT_WRAPPING);
             StaticLayout.Builder builder = StaticLayout.Builder.obtain(mTransformed,
                     0, mTransformed.length(), mTextPaint, wantWidth)
                     .setAlignment(alignment)
@@ -10774,7 +10749,7 @@
                     .setJustificationMode(mJustificationMode)
                     .setMaxLines(mMaxMode == LINES ? mMaximum : Integer.MAX_VALUE)
                     .setLineBreakConfig(LineBreakConfig.getLineBreakConfig(
-                            mLineBreakStyle, mLineBreakWordStyle, autoPhraseBreaking));
+                            mLineBreakStyle, mLineBreakWordStyle));
             if (shouldEllipsize) {
                 builder.setEllipsize(effectiveEllipsize)
                         .setEllipsizedWidth(ellipsisWidth);
@@ -11132,9 +11107,6 @@
 
         final StaticLayout.Builder layoutBuilder = StaticLayout.Builder.obtain(
                 text, 0, text.length(),  mTempTextPaint, Math.round(availableSpace.right));
-        final boolean autoPhraseBreaking =
-                !mUserSpeficiedLineBreakwordStyle && FeatureFlagUtils.isEnabled(mContext,
-                        FeatureFlagUtils.SETTINGS_AUTO_TEXT_WRAPPING);
         layoutBuilder.setAlignment(getLayoutAlignment())
                 .setLineSpacing(getLineSpacingExtra(), getLineSpacingMultiplier())
                 .setIncludePad(getIncludeFontPadding())
@@ -11145,7 +11117,7 @@
                 .setMaxLines(mMaxMode == LINES ? mMaximum : Integer.MAX_VALUE)
                 .setTextDirection(getTextDirectionHeuristic())
                 .setLineBreakConfig(LineBreakConfig.getLineBreakConfig(
-                        mLineBreakStyle, mLineBreakWordStyle, autoPhraseBreaking));
+                        mLineBreakStyle, mLineBreakWordStyle));
 
         final StaticLayout layout = layoutBuilder.build();
 
diff --git a/core/java/android/window/WindowMetricsController.java b/core/java/android/window/WindowMetricsController.java
index 954f686..2858f0a 100644
--- a/core/java/android/window/WindowMetricsController.java
+++ b/core/java/android/window/WindowMetricsController.java
@@ -145,13 +145,13 @@
         for (int i = 0; i < possibleDisplayInfos.size(); i++) {
             currentDisplayInfo = possibleDisplayInfos.get(i);
 
-            // Calculate max bounds for this rotation and state.
-            Rect maxBounds = new Rect(0, 0, currentDisplayInfo.logicalWidth,
-                    currentDisplayInfo.logicalHeight);
+            // Calculate max bounds for natural rotation and state.
+            Rect maxBounds = new Rect(0, 0, currentDisplayInfo.getNaturalWidth(),
+                    currentDisplayInfo.getNaturalHeight());
 
-            // Calculate insets for the rotated max bounds.
+            // Calculate insets for the natural max bounds.
             final boolean isScreenRound = (currentDisplayInfo.flags & Display.FLAG_ROUND) != 0;
-            // Initialize insets based upon display rotation. Note any window-provided insets
+            // Initialize insets based on Surface.ROTATION_0. Note any window-provided insets
             // will not be set.
             windowInsets = getWindowInsetsFromServerForDisplay(
                     currentDisplayInfo.displayId, null /* token */,
diff --git a/core/java/android/window/WindowProviderService.java b/core/java/android/window/WindowProviderService.java
index f2ae973..611da3c 100644
--- a/core/java/android/window/WindowProviderService.java
+++ b/core/java/android/window/WindowProviderService.java
@@ -34,6 +34,7 @@
 import android.hardware.display.DisplayManager;
 import android.os.Bundle;
 import android.os.IBinder;
+import android.util.Log;
 import android.view.Display;
 import android.view.WindowManager;
 import android.view.WindowManager.LayoutParams.WindowType;
@@ -52,6 +53,8 @@
 @UiContext
 public abstract class WindowProviderService extends Service implements WindowProvider {
 
+    private static final String TAG = WindowProviderService.class.getSimpleName();
+
     private final Bundle mOptions;
     private final WindowTokenClient mWindowToken = new WindowTokenClient();
     private final WindowContextController mController = new WindowContextController(mWindowToken);
@@ -194,8 +197,16 @@
     public final Context createServiceBaseContext(ActivityThread mainThread,
             LoadedApk packageInfo) {
         final Context context = super.createServiceBaseContext(mainThread, packageInfo);
-        final Display display = context.getSystemService(DisplayManager.class)
-                .getDisplay(getInitialDisplayId());
+        final DisplayManager displayManager = context.getSystemService(DisplayManager.class);
+        final int initialDisplayId = getInitialDisplayId();
+        Display display = displayManager.getDisplay(initialDisplayId);
+        // Fallback to use the default display if the initial display to start WindowProviderService
+        // is detached.
+        if (display == null) {
+            Log.e(TAG, "Display with id " + initialDisplayId + " not found, falling back to "
+                    + "DEFAULT_DISPLAY");
+            display = displayManager.getDisplay(DEFAULT_DISPLAY);
+        }
         return context.createTokenContext(mWindowToken, display);
     }
 
diff --git a/core/java/com/android/internal/accessibility/common/MagnificationConstants.java b/core/java/com/android/internal/accessibility/common/MagnificationConstants.java
index 95c3419..2c49303 100644
--- a/core/java/com/android/internal/accessibility/common/MagnificationConstants.java
+++ b/core/java/com/android/internal/accessibility/common/MagnificationConstants.java
@@ -17,7 +17,7 @@
 package com.android.internal.accessibility.common;
 
 /**
- * Collection of common constants for accessibility shortcut.
+ * Collection of common constants for accessibility magnification.
  */
 public final class MagnificationConstants {
     private MagnificationConstants() {}
diff --git a/core/java/com/android/internal/app/IntentForwarderActivity.java b/core/java/com/android/internal/app/IntentForwarderActivity.java
index 7452daa..65b5979 100644
--- a/core/java/com/android/internal/app/IntentForwarderActivity.java
+++ b/core/java/com/android/internal/app/IntentForwarderActivity.java
@@ -56,6 +56,7 @@
 import android.os.UserManager;
 import android.provider.Settings;
 import android.telecom.TelecomManager;
+import android.util.Log;
 import android.util.Slog;
 import android.view.View;
 import android.widget.Button;
@@ -124,16 +125,19 @@
         String className = intentReceived.getComponent().getClassName();
         final int targetUserId;
         final String userMessage;
+        final UserInfo managedProfile;
         if (className.equals(FORWARD_INTENT_TO_PARENT)) {
             userMessage = getForwardToPersonalMessage();
             targetUserId = getProfileParent();
+            managedProfile = null;
 
             getMetricsLogger().write(
                     new LogMaker(MetricsEvent.ACTION_SWITCH_SHARE_PROFILE)
                     .setSubtype(MetricsEvent.PARENT_PROFILE));
         } else if (className.equals(FORWARD_INTENT_TO_MANAGED_PROFILE)) {
             userMessage = getForwardToWorkMessage();
-            targetUserId = getManagedProfile();
+            managedProfile = getManagedProfile();
+            targetUserId = managedProfile == null ? UserHandle.USER_NULL : managedProfile.id;
 
             getMetricsLogger().write(
                     new LogMaker(MetricsEvent.ACTION_SWITCH_SHARE_PROFILE)
@@ -142,6 +146,7 @@
             Slog.wtf(TAG, IntentForwarderActivity.class.getName() + " cannot be called directly");
             userMessage = null;
             targetUserId = UserHandle.USER_NULL;
+            managedProfile = null;
         }
         if (targetUserId == UserHandle.USER_NULL) {
             // This covers the case where there is no parent / managed profile.
@@ -185,27 +190,49 @@
                         finish();
                     // When switching to the work profile, ask the user for consent before launching
                     } else if (className.equals(FORWARD_INTENT_TO_MANAGED_PROFILE)) {
-                        maybeShowUserConsentMiniResolver(result, newIntent, targetUserId);
+                        maybeShowUserConsentMiniResolver(result, newIntent, managedProfile);
                     }
                 }, getApplicationContext().getMainExecutor());
     }
 
     private void maybeShowUserConsentMiniResolver(
-            ResolveInfo target, Intent launchIntent, int targetUserId) {
+            ResolveInfo target, Intent launchIntent, UserInfo managedProfile) {
         if (target == null || isIntentForwarderResolveInfo(target) || !isDeviceProvisioned()) {
             finish();
             return;
         }
 
-        if (launchIntent.getBooleanExtra(EXTRA_SKIP_USER_CONFIRMATION, /* defaultValue= */ false)
-                && getCallingPackage() != null
-                && PERMISSION_GRANTED == getPackageManager().checkPermission(
-                        INTERACT_ACROSS_USERS, getCallingPackage())) {
+        int targetUserId = managedProfile == null ? UserHandle.USER_NULL : managedProfile.id;
+        String callingPackage = getCallingPackage();
+        boolean privilegedCallerAskedToSkipUserConsent =
+                launchIntent.getBooleanExtra(
+                        EXTRA_SKIP_USER_CONFIRMATION, /* defaultValue= */ false)
+                        && callingPackage != null
+                        && PERMISSION_GRANTED == getPackageManager().checkPermission(
+                              INTERACT_ACROSS_USERS, callingPackage);
+
+        DevicePolicyManager devicePolicyManager =
+                getSystemService(DevicePolicyManager.class);
+        ComponentName profileOwnerName = devicePolicyManager.getProfileOwnerAsUser(targetUserId);
+        boolean intentToLaunchProfileOwner = profileOwnerName != null
+                && profileOwnerName.getPackageName().equals(target.getComponentInfo().packageName);
+
+        if (privilegedCallerAskedToSkipUserConsent || intentToLaunchProfileOwner) {
+            Log.i("IntentForwarderActivity", String.format(
+                    "Skipping user consent for redirection into the managed profile for intent [%s]"
+                            + ", privilegedCallerAskedToSkipUserConsent=[%s]"
+                            + ", intentToLaunchProfileOwner=[%s]",
+                    launchIntent, privilegedCallerAskedToSkipUserConsent,
+                    intentToLaunchProfileOwner));
             startActivityAsCaller(launchIntent, targetUserId);
             finish();
             return;
         }
 
+        Log.i("IntentForwarderActivity", String.format(
+                "Showing user consent for redirection into the managed profile for intent [%s] and "
+                        + " calling package [%s]",
+                launchIntent, callingPackage));
         int layoutId = R.layout.miniresolver;
         setContentView(layoutId);
 
@@ -245,8 +272,7 @@
 
 
         View telephonyInfo = findViewById(R.id.miniresolver_info_section);
-        DevicePolicyManager devicePolicyManager =
-                getSystemService(DevicePolicyManager.class);
+
         // Additional information section is work telephony specific. Therefore, it is only shown
         // for telephony related intents, when all sim subscriptions are in the work profile.
         if ((isDialerIntent(launchIntent) || isTextMessageIntent(launchIntent))
@@ -507,20 +533,18 @@
     }
 
     /**
-     * Returns the userId of the managed profile for this device or UserHandle.USER_NULL if there is
-     * no managed profile.
+     * Returns the managed profile for this device or null if there is no managed profile.
      *
-     * TODO: Remove the assumption that there is only one managed profile
-     * on the device.
+     * TODO: Remove the assumption that there is only one managed profile on the device.
      */
-    private int getManagedProfile() {
+    @Nullable private UserInfo getManagedProfile() {
         List<UserInfo> relatedUsers = mInjector.getUserManager().getProfiles(UserHandle.myUserId());
         for (UserInfo userInfo : relatedUsers) {
-            if (userInfo.isManagedProfile()) return userInfo.id;
+            if (userInfo.isManagedProfile()) return userInfo;
         }
         Slog.wtf(TAG, FORWARD_INTENT_TO_MANAGED_PROFILE
                 + " has been called, but there is no managed profile");
-        return UserHandle.USER_NULL;
+        return null;
     }
 
     /**
diff --git a/core/java/com/android/internal/policy/DecorView.java b/core/java/com/android/internal/policy/DecorView.java
index af1fdd7..bb86801 100644
--- a/core/java/com/android/internal/policy/DecorView.java
+++ b/core/java/com/android/internal/policy/DecorView.java
@@ -232,7 +232,7 @@
     private boolean mLastHasRightStableInset = false;
     private boolean mLastHasLeftStableInset = false;
     private int mLastWindowFlags = 0;
-    private boolean mLastShouldAlwaysConsumeSystemBars = false;
+    private @InsetsType int mLastForceConsumingTypes = 0;
     private @InsetsType int mLastSuppressScrimTypes = 0;
 
     private int mRootScrollY = 0;
@@ -1111,19 +1111,19 @@
                     : controller.getSystemBarsAppearance();
 
             if (insets != null) {
-                mLastShouldAlwaysConsumeSystemBars = insets.shouldAlwaysConsumeSystemBars();
+                mLastForceConsumingTypes = insets.getForceConsumingTypes();
 
-                final boolean clearsCompatInsets =
-                        clearsCompatInsets(attrs.type, attrs.flags,
-                                getResources().getConfiguration().windowConfiguration
-                                        .getWindowingMode())
-                        && !mLastShouldAlwaysConsumeSystemBars;
+                @InsetsType int compatInsetsTypes =
+                        WindowInsets.Type.systemBars() | WindowInsets.Type.displayCutout();
+                if (clearsCompatInsets(attrs.type, attrs.flags,
+                        getResources().getConfiguration().windowConfiguration.getWindowingMode())) {
+                    compatInsetsTypes &= mLastForceConsumingTypes;
+                }
                 final Insets stableBarInsets = insets.getInsetsIgnoringVisibility(
                         WindowInsets.Type.systemBars());
-                final Insets systemInsets = clearsCompatInsets
+                final Insets systemInsets = compatInsetsTypes == 0
                         ? Insets.NONE
-                        : Insets.min(insets.getInsets(WindowInsets.Type.systemBars()
-                                | WindowInsets.Type.displayCutout()), stableBarInsets);
+                        : Insets.min(insets.getInsets(compatInsetsTypes), stableBarInsets);
                 mLastTopInset = systemInsets.top;
                 mLastBottomInset = systemInsets.bottom;
                 mLastRightInset = systemInsets.right;
@@ -1208,7 +1208,8 @@
                         && (sysUiVisibility & SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION) == 0
                         && decorFitsSystemWindows
                         && !hideNavigation)
-                || (mLastShouldAlwaysConsumeSystemBars && hideNavigation);
+                || ((mLastForceConsumingTypes & WindowInsets.Type.navigationBars()) != 0
+                        && hideNavigation);
 
         boolean consumingNavBar =
                 ((attrs.flags & FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS) != 0
@@ -1224,13 +1225,15 @@
         boolean fullscreen = (sysUiVisibility & SYSTEM_UI_FLAG_FULLSCREEN) != 0
                 || (attrs.flags & FLAG_FULLSCREEN) != 0
                 || (requestedVisibleTypes & WindowInsets.Type.statusBars()) == 0;
-        boolean consumingStatusBar = (sysUiVisibility & SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN) == 0
-                && decorFitsSystemWindows
-                && (attrs.flags & FLAG_LAYOUT_IN_SCREEN) == 0
-                && (attrs.flags & FLAG_LAYOUT_INSET_DECOR) == 0
-                && mForceWindowDrawsBarBackgrounds
-                && mLastTopInset != 0
-                || (mLastShouldAlwaysConsumeSystemBars && fullscreen);
+        boolean consumingStatusBar =
+                ((sysUiVisibility & SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN) == 0
+                        && decorFitsSystemWindows
+                        && (attrs.flags & FLAG_LAYOUT_IN_SCREEN) == 0
+                        && (attrs.flags & FLAG_LAYOUT_INSET_DECOR) == 0
+                        && mForceWindowDrawsBarBackgrounds
+                        && mLastTopInset != 0)
+                || ((mLastForceConsumingTypes & WindowInsets.Type.statusBars()) != 0
+                        && fullscreen);
 
         int consumedTop = consumingStatusBar ? mLastTopInset : 0;
         int consumedRight = consumingNavBar ? mLastRightInset : 0;
@@ -1434,9 +1437,9 @@
     private void updateColorViewInt(final ColorViewState state, int color, int dividerColor,
             int size, boolean verticalBar, boolean seascape, int sideMargin, boolean animate,
             boolean force, @InsetsType int requestedVisibleTypes) {
+        final @InsetsType int type = state.attributes.insetsType;
         state.present = state.attributes.isPresent(
-                (requestedVisibleTypes & state.attributes.insetsType) != 0
-                        || mLastShouldAlwaysConsumeSystemBars,
+                (requestedVisibleTypes & type) != 0 || (mLastForceConsumingTypes & type) != 0,
                 mWindow.getAttributes().flags, force);
         boolean show = state.attributes.isVisible(state.present, color,
                 mWindow.getAttributes().flags, force);
diff --git a/core/jni/android_os_GraphicsEnvironment.cpp b/core/jni/android_os_GraphicsEnvironment.cpp
index afc3cbd..8fc30d1 100644
--- a/core/jni/android_os_GraphicsEnvironment.cpp
+++ b/core/jni/android_os_GraphicsEnvironment.cpp
@@ -50,7 +50,7 @@
                                                     appPackageNameChars.c_str(), vulkanVersion);
 }
 
-void setAngleInfo_native(JNIEnv* env, jobject clazz, jstring path, jboolean useSystemAngle,
+void setAngleInfo_native(JNIEnv* env, jobject clazz, jstring path, jboolean useNativeDriver,
                          jstring packageName, jobjectArray featuresObj) {
     ScopedUtfChars pathChars(env, path);
     ScopedUtfChars packageNameChars(env, packageName);
@@ -73,7 +73,7 @@
         }
     }
 
-    android::GraphicsEnv::getInstance().setAngleInfo(pathChars.c_str(), useSystemAngle,
+    android::GraphicsEnv::getInstance().setAngleInfo(pathChars.c_str(), useNativeDriver,
                                                      packageNameChars.c_str(), features);
 }
 
@@ -118,7 +118,7 @@
          reinterpret_cast<void*>(setGpuStats_native)},
         {"setInjectLayersPrSetDumpable", "()Z",
          reinterpret_cast<void*>(setInjectLayersPrSetDumpable_native)},
-        {"setAngleInfo", "(Ljava/lang/String;ZLjava/lang/String;[Ljava/lang/String;)V",
+        {"nativeSetAngleInfo", "(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/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 2f9f6ae..a01c7b6 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -6094,7 +6094,7 @@
     <!-- @hide @SystemApi Allows an application to observe usage time of apps. The app can register
          for callbacks when apps reach a certain usage time limit, etc. -->
     <permission android:name="android.permission.OBSERVE_APP_USAGE"
-        android:protectionLevel="signature|privileged" />
+        android:protectionLevel="signature|privileged|role" />
 
     <!-- @hide @TestApi @SystemApi Allows an application to change the app idle state of an app.
          <p>Not for use by third-party applications. -->
@@ -6724,7 +6724,7 @@
          it will be ignored.
         @hide -->
     <permission android:name="android.permission.MODIFY_DAY_NIGHT_MODE"
-      android:protectionLevel="signature|privileged" />
+      android:protectionLevel="signature|privileged|role" />
 
     <!-- @SystemApi Allows entering or exiting car mode using a specified priority.
         This permission is required to use UiModeManager while specifying a priority for the calling
diff --git a/core/tests/coretests/src/android/text/StaticLayoutTest.java b/core/tests/coretests/src/android/text/StaticLayoutTest.java
index 925da49..0ebf03f 100644
--- a/core/tests/coretests/src/android/text/StaticLayoutTest.java
+++ b/core/tests/coretests/src/android/text/StaticLayoutTest.java
@@ -24,7 +24,6 @@
 import android.graphics.Canvas;
 import android.graphics.Paint;
 import android.graphics.Paint.FontMetricsInt;
-import android.graphics.text.LineBreakConfig;
 import android.os.LocaleList;
 import android.platform.test.annotations.Presubmit;
 import android.text.Layout.Alignment;
@@ -926,24 +925,4 @@
         assertEquals(0, layout.getHeight(true));
         assertEquals(2, layout.getLineCount());
     }
-
-    @Test
-    public void testBuilder_autoPhraseBreaking() {
-        {
-            // setAutoPhraseBreaking true
-            LineBreakConfig lineBreakConfig = new LineBreakConfig.Builder()
-                    .setLineBreakWordStyle(LineBreakConfig.LINE_BREAK_STYLE_NONE)
-                    .setLineBreakWordStyle(LineBreakConfig.LINE_BREAK_WORD_STYLE_NONE)
-                    .setAutoPhraseBreaking(true)
-                    .build();
-            final String text = "これが正解。";
-            // Obtain.
-            StaticLayout.Builder builder = StaticLayout.Builder.obtain(text, 0,
-                    text.length(), mDefaultPaint, DEFAULT_OUTER_WIDTH);
-            builder.setLineBreakConfig(lineBreakConfig);
-            builder.build();
-            assertEquals(LineBreakConfig.LINE_BREAK_WORD_STYLE_PHRASE,
-                    builder.getLineBreakWordStyle());
-        }
-    }
 }
diff --git a/core/tests/coretests/src/android/view/WindowInsetsTest.java b/core/tests/coretests/src/android/view/WindowInsetsTest.java
index b4ba23c..69abf5f 100644
--- a/core/tests/coretests/src/android/view/WindowInsetsTest.java
+++ b/core/tests/coretests/src/android/view/WindowInsetsTest.java
@@ -40,14 +40,14 @@
     @Test
     public void systemWindowInsets_afterConsuming_isConsumed() {
         assertTrue(new WindowInsets(WindowInsets.createCompatTypeMap(new Rect(1, 2, 3, 4)), null,
-                null, false, false, 0, null, null, null, null,
+                null, false, 0, 0, null, null, null, null,
                 WindowInsets.Type.systemBars(), false)
                 .consumeSystemWindowInsets().isConsumed());
     }
 
     @Test
     public void multiNullConstructor_isConsumed() {
-        assertTrue(new WindowInsets(null, null, null, false, false, 0, null, null, null, null,
+        assertTrue(new WindowInsets(null, null, null, false, 0, 0, null, null, null, null,
                 WindowInsets.Type.systemBars(), false).isConsumed());
     }
 
@@ -63,7 +63,7 @@
         boolean[] visible = new boolean[SIZE];
         WindowInsets.assignCompatInsets(maxInsets, new Rect(0, 10, 0, 0));
         WindowInsets.assignCompatInsets(insets, new Rect(0, 0, 0, 0));
-        WindowInsets windowInsets = new WindowInsets(insets, maxInsets, visible, false, false,
+        WindowInsets windowInsets = new WindowInsets(insets, maxInsets, visible, false, 0,
                 0, null, null, null, DisplayShape.NONE, systemBars(),
                 true /* compatIgnoreVisibility */);
         assertEquals(Insets.of(0, 10, 0, 0), windowInsets.getSystemWindowInsets());
diff --git a/core/tests/coretests/src/com/android/internal/widget/ActionBarOverlayLayoutTest.java b/core/tests/coretests/src/com/android/internal/widget/ActionBarOverlayLayoutTest.java
index a1a4265..84dd274 100644
--- a/core/tests/coretests/src/com/android/internal/widget/ActionBarOverlayLayoutTest.java
+++ b/core/tests/coretests/src/com/android/internal/widget/ActionBarOverlayLayoutTest.java
@@ -169,7 +169,7 @@
 
     private WindowInsets insetsWith(Insets content, DisplayCutout cutout) {
         return new WindowInsets(WindowInsets.createCompatTypeMap(content.toRect()), null, null,
-                false, false, 0, cutout, null, null, null, WindowInsets.Type.systemBars(), false);
+                false, 0, 0, cutout, null, null, null, WindowInsets.Type.systemBars(), false);
     }
 
     private ViewGroup createViewGroupWithId(int id) {
diff --git a/graphics/java/android/graphics/text/LineBreakConfig.java b/graphics/java/android/graphics/text/LineBreakConfig.java
index d0327159..0c493f5 100644
--- a/graphics/java/android/graphics/text/LineBreakConfig.java
+++ b/graphics/java/android/graphics/text/LineBreakConfig.java
@@ -94,11 +94,6 @@
         private @LineBreakWordStyle int mLineBreakWordStyle =
                 LineBreakConfig.LINE_BREAK_WORD_STYLE_NONE;
 
-        // Whether or not enabling phrase breaking automatically.
-        // TODO(b/226012260): Remove this and add LINE_BREAK_WORD_STYLE_PHRASE_AUTO after
-        // the experiment.
-        private boolean mAutoPhraseBreaking = false;
-
         /**
          * Builder constructor.
          */
@@ -128,22 +123,12 @@
         }
 
         /**
-         * Enables or disables the automation of {@link LINE_BREAK_WORD_STYLE_PHRASE}.
-         *
-         * @hide
-         */
-        public @NonNull Builder setAutoPhraseBreaking(boolean autoPhraseBreaking) {
-            mAutoPhraseBreaking = autoPhraseBreaking;
-            return this;
-        }
-
-        /**
          * Builds a {@link LineBreakConfig} instance.
          *
          * @return The {@code LineBreakConfig} instance.
          */
         public @NonNull LineBreakConfig build() {
-            return new LineBreakConfig(mLineBreakStyle, mLineBreakWordStyle, mAutoPhraseBreaking);
+            return new LineBreakConfig(mLineBreakStyle, mLineBreakWordStyle);
         }
     }
 
@@ -164,23 +149,6 @@
                 .build();
     }
 
-    /**
-     * Create the LineBreakConfig instance.
-     *
-     * @param lineBreakStyle the line break style for text wrapping.
-     * @param lineBreakWordStyle the line break word style for text wrapping.
-     * @return the {@link LineBreakConfig} instance.     *
-     * @hide
-     */
-    public static @NonNull LineBreakConfig getLineBreakConfig(@LineBreakStyle int lineBreakStyle,
-            @LineBreakWordStyle int lineBreakWordStyle, boolean autoPhraseBreaking) {
-        LineBreakConfig.Builder builder = new LineBreakConfig.Builder();
-        return builder.setLineBreakStyle(lineBreakStyle)
-                .setLineBreakWordStyle(lineBreakWordStyle)
-                .setAutoPhraseBreaking(autoPhraseBreaking)
-                .build();
-    }
-
     /** @hide */
     public static final LineBreakConfig NONE =
             new Builder().setLineBreakStyle(LINE_BREAK_STYLE_NONE)
@@ -188,7 +156,6 @@
 
     private final @LineBreakStyle int mLineBreakStyle;
     private final @LineBreakWordStyle int mLineBreakWordStyle;
-    private final boolean mAutoPhraseBreaking;
 
     /**
      * Constructor with line-break parameters.
@@ -197,10 +164,9 @@
      * {@code LineBreakConfig} instance.
      */
     private LineBreakConfig(@LineBreakStyle int lineBreakStyle,
-            @LineBreakWordStyle int lineBreakWordStyle, boolean autoPhraseBreaking) {
+            @LineBreakWordStyle int lineBreakWordStyle) {
         mLineBreakStyle = lineBreakStyle;
         mLineBreakWordStyle = lineBreakWordStyle;
-        mAutoPhraseBreaking = autoPhraseBreaking;
     }
 
     /**
@@ -221,17 +187,6 @@
         return mLineBreakWordStyle;
     }
 
-    /**
-     * Used to identify if the automation of {@link LINE_BREAK_WORD_STYLE_PHRASE} is enabled.
-     *
-     * @return The result that records whether or not the automation of
-     * {@link LINE_BREAK_WORD_STYLE_PHRASE} is enabled.
-     * @hide
-     */
-    public boolean getAutoPhraseBreaking() {
-        return mAutoPhraseBreaking;
-    }
-
     @Override
     public boolean equals(Object o) {
         if (o == null) return false;
@@ -239,8 +194,7 @@
         if (!(o instanceof LineBreakConfig)) return false;
         LineBreakConfig that = (LineBreakConfig) o;
         return (mLineBreakStyle == that.mLineBreakStyle)
-                && (mLineBreakWordStyle == that.mLineBreakWordStyle)
-                && (mAutoPhraseBreaking == that.mAutoPhraseBreaking);
+                && (mLineBreakWordStyle == that.mLineBreakWordStyle);
     }
 
     @Override
diff --git a/libs/WindowManager/Shell/res/values/dimen.xml b/libs/WindowManager/Shell/res/values/dimen.xml
index 14e8253..3d4b55a 100644
--- a/libs/WindowManager/Shell/res/values/dimen.xml
+++ b/libs/WindowManager/Shell/res/values/dimen.xml
@@ -228,12 +228,12 @@
     <dimen name="bubble_user_education_stack_padding">16dp</dimen>
     <!-- Size of the bubble bar (height), should match transient_taskbar_size in Launcher. -->
     <dimen name="bubblebar_size">72dp</dimen>
-    <!-- The size of the drag handle / menu shown along with a bubble bar expanded view. -->
-    <dimen name="bubble_bar_expanded_view_handle_size">40dp</dimen>
-    <!-- The width of the drag handle shown along with a bubble bar expanded view. -->
-    <dimen name="bubble_bar_expanded_view_handle_width">128dp</dimen>
-    <!-- The height of the drag handle shown along with a bubble bar expanded view. -->
-    <dimen name="bubble_bar_expanded_view_handle_height">4dp</dimen>
+    <!-- The size of the caption bar inset at the top of bubble bar expanded view. -->
+    <dimen name="bubble_bar_expanded_view_caption_height">32dp</dimen>
+    <!-- The height of the dots shown for the caption menu in the bubble bar expanded view.. -->
+    <dimen name="bubble_bar_expanded_view_caption_dot_size">4dp</dimen>
+    <!-- The spacing between the dots for the caption menu in the bubble bar expanded view.. -->
+    <dimen name="bubble_bar_expanded_view_caption_dot_spacing">4dp</dimen>
     <!-- Minimum width of the bubble bar manage menu. -->
     <dimen name="bubble_bar_manage_menu_min_width">200dp</dimen>
     <!-- Size of the dismiss icon in the bubble bar manage menu. -->
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarAnimationHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarAnimationHelper.java
index f729d02..e97390d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarAnimationHelper.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarAnimationHelper.java
@@ -25,6 +25,8 @@
 import android.util.Log;
 import android.widget.FrameLayout;
 
+import androidx.annotation.Nullable;
+
 import com.android.wm.shell.animation.Interpolators;
 import com.android.wm.shell.animation.PhysicsAnimator;
 import com.android.wm.shell.bubbles.BubbleOverflow;
@@ -111,7 +113,8 @@
     /**
      * Animates the provided bubble's expanded view to the expanded state.
      */
-    public void animateExpansion(BubbleViewProvider expandedBubble) {
+    public void animateExpansion(BubbleViewProvider expandedBubble,
+            @Nullable Runnable afterAnimation) {
         mExpandedBubble = expandedBubble;
         if (mExpandedBubble == null) {
             return;
@@ -160,6 +163,9 @@
                     bev.setAnimationMatrix(null);
                     updateExpandedView();
                     bev.setSurfaceZOrderedOnTop(false);
+                    if (afterAnimation != null) {
+                        afterAnimation.run();
+                    }
                 })
                 .start();
     }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedView.java
index 396aa0e..6b6d6ba 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedView.java
@@ -16,12 +16,12 @@
 
 package com.android.wm.shell.bubbles.bar;
 
-import android.annotation.ColorInt;
 import android.annotation.Nullable;
 import android.app.ActivityManager;
 import android.content.Context;
 import android.content.res.TypedArray;
 import android.graphics.Color;
+import android.graphics.Insets;
 import android.graphics.Outline;
 import android.graphics.Rect;
 import android.util.AttributeSet;
@@ -63,7 +63,8 @@
     private @Nullable TaskView mTaskView;
     private @Nullable BubbleOverflowContainerView mOverflowView;
 
-    private int mHandleHeight;
+    private int mCaptionHeight;
+
     private int mBackgroundColor;
     private float mCornerRadius = 0f;
 
@@ -97,8 +98,8 @@
         super.onFinishInflate();
         Context context = getContext();
         setElevation(getResources().getDimensionPixelSize(R.dimen.bubble_elevation));
-        mHandleHeight = context.getResources().getDimensionPixelSize(
-                R.dimen.bubble_bar_expanded_view_handle_size);
+        mCaptionHeight = context.getResources().getDimensionPixelSize(
+                R.dimen.bubble_bar_expanded_view_caption_height);
         addView(mHandleView);
         applyThemeAttrs();
         setClipToOutline(true);
@@ -136,6 +137,9 @@
             addView(mTaskView);
             mTaskView.setEnableSurfaceClipping(true);
             mTaskView.setCornerRadius(mCornerRadius);
+
+            // Handle view needs to draw on top of task view.
+            bringChildToFront(mHandleView);
         }
         mMenuViewController = new BubbleBarMenuViewController(mContext, this);
         mMenuViewController.setListener(new BubbleBarMenuViewController.Listener() {
@@ -169,6 +173,10 @@
         });
     }
 
+    public BubbleBarHandleView getHandleView() {
+        return mHandleView;
+    }
+
     // TODO (b/275087636): call this when theme/config changes
     /** Updates the view based on the current theme. */
     public void applyThemeAttrs() {
@@ -183,12 +191,12 @@
 
         ta.recycle();
 
-        mHandleHeight = getResources().getDimensionPixelSize(
-                R.dimen.bubble_bar_expanded_view_handle_size);
+        mCaptionHeight = getResources().getDimensionPixelSize(
+                R.dimen.bubble_bar_expanded_view_caption_height);
 
         if (mTaskView != null) {
             mTaskView.setCornerRadius(mCornerRadius);
-            updateHandleAndBackgroundColor(true /* animated */);
+            updateHandleColor(true /* animated */);
         }
     }
 
@@ -196,13 +204,12 @@
     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
         super.onMeasure(widthMeasureSpec, heightMeasureSpec);
         int height = MeasureSpec.getSize(heightMeasureSpec);
-        int menuViewHeight = Math.min(mHandleHeight, height);
+        int menuViewHeight = Math.min(mCaptionHeight, height);
         measureChild(mHandleView, widthMeasureSpec, MeasureSpec.makeMeasureSpec(menuViewHeight,
                 MeasureSpec.getMode(heightMeasureSpec)));
 
         if (mTaskView != null) {
-            int taskViewHeight = height - menuViewHeight;
-            measureChild(mTaskView, widthMeasureSpec, MeasureSpec.makeMeasureSpec(taskViewHeight,
+            measureChild(mTaskView, widthMeasureSpec, MeasureSpec.makeMeasureSpec(height,
                     MeasureSpec.getMode(heightMeasureSpec)));
         }
     }
@@ -210,19 +217,20 @@
     @Override
     protected void onLayout(boolean changed, int l, int t, int r, int b) {
         super.onLayout(changed, l, t, r, b);
-        // Drag handle above
-        final int dragHandleBottom = t + mHandleView.getMeasuredHeight();
-        mHandleView.layout(l, t, r, dragHandleBottom);
+        final int captionBottom = t + mCaptionHeight;
         if (mTaskView != null) {
-            mTaskView.layout(l, dragHandleBottom, r,
-                    dragHandleBottom + mTaskView.getMeasuredHeight());
+            mTaskView.layout(l, t, r,
+                    t + mTaskView.getMeasuredHeight());
+            mTaskView.setCaptionInsets(Insets.of(0, mCaptionHeight, 0, 0));
         }
+        // Handle draws on top of task view in the caption area.
+        mHandleView.layout(l, t, r, captionBottom);
     }
 
     @Override
     public void onTaskCreated() {
         setContentVisibility(true);
-        updateHandleAndBackgroundColor(false /* animated */);
+        updateHandleColor(false /* animated */);
     }
 
     @Override
@@ -298,33 +306,20 @@
     }
 
     /**
-     * Updates the background color to match with task view status/bg color, and sets handle color
-     * to contrast with the background
+     * Updates the handle color based on the task view status bar or background color; if those
+     * are transparent it defaults to the background color pulled from system theme attributes.
      */
-    private void updateHandleAndBackgroundColor(boolean animated) {
-        if (mTaskView == null) return;
-        final int color = getTaskViewColor();
-        final boolean isRegionDark = Color.luminance(color) <= 0.5;
-        mHandleView.updateHandleColor(isRegionDark, animated);
-        setBackgroundColor(color);
-    }
-
-    /**
-     * Retrieves task view status/nav bar color or background if available
-     *
-     * TODO (b/283075226): Update with color sampling when
-     *                     RegionSamplingHelper or alternative is available
-     */
-    private @ColorInt int getTaskViewColor() {
-        if (mTaskView == null || mTaskView.getTaskInfo() == null) return mBackgroundColor;
+    private void updateHandleColor(boolean animated) {
+        if (mTaskView == null || mTaskView.getTaskInfo() == null) return;
+        int color = mBackgroundColor;
         ActivityManager.TaskDescription taskDescription = mTaskView.getTaskInfo().taskDescription;
         if (taskDescription.getStatusBarColor() != Color.TRANSPARENT) {
-            return taskDescription.getStatusBarColor();
+            color = taskDescription.getStatusBarColor();
         } else if (taskDescription.getBackgroundColor() != Color.TRANSPARENT) {
-            return taskDescription.getBackgroundColor();
-        } else {
-            return mBackgroundColor;
+            color = taskDescription.getBackgroundColor();
         }
+        final boolean isRegionDark = Color.luminance(color) <= 0.5;
+        mHandleView.updateHandleColor(isRegionDark, animated);
     }
 
     /**
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarHandleView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarHandleView.java
index ce26bc0..2b7a070 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarHandleView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarHandleView.java
@@ -21,7 +21,8 @@
 import android.annotation.Nullable;
 import android.content.Context;
 import android.graphics.Outline;
-import android.graphics.Rect;
+import android.graphics.Path;
+import android.graphics.RectF;
 import android.util.AttributeSet;
 import android.view.View;
 import android.view.ViewOutlineProvider;
@@ -37,8 +38,12 @@
 public class BubbleBarHandleView extends View {
     private static final long COLOR_CHANGE_DURATION = 120;
 
-    private int mHandleWidth;
-    private int mHandleHeight;
+    // The handle view is currently rendered as 3 evenly spaced dots.
+    private int mDotSize;
+    private int mDotSpacing;
+    // Path used to draw the dots
+    private final Path mPath = new Path();
+
     private @ColorInt int mHandleLightColor;
     private @ColorInt int mHandleDarkColor;
     private @Nullable ObjectAnimator mColorChangeAnim;
@@ -58,11 +63,10 @@
     public BubbleBarHandleView(Context context, AttributeSet attrs, int defStyleAttr,
             int defStyleRes) {
         super(context, attrs, defStyleAttr, defStyleRes);
-
-        mHandleWidth = getResources().getDimensionPixelSize(
-                R.dimen.bubble_bar_expanded_view_handle_width);
-        mHandleHeight = getResources().getDimensionPixelSize(
-                R.dimen.bubble_bar_expanded_view_handle_height);
+        mDotSize = getResources().getDimensionPixelSize(
+                R.dimen.bubble_bar_expanded_view_caption_dot_size);
+        mDotSpacing = getResources().getDimensionPixelSize(
+                R.dimen.bubble_bar_expanded_view_caption_dot_spacing);
         mHandleLightColor = ContextCompat.getColor(getContext(),
                 R.color.bubble_bar_expanded_view_handle_light);
         mHandleDarkColor = ContextCompat.getColor(getContext(),
@@ -74,13 +78,26 @@
             public void getOutline(View view, Outline outline) {
                 final int handleCenterX = view.getWidth() / 2;
                 final int handleCenterY = view.getHeight() / 2;
-                final float handleRadius = mHandleHeight / 2f;
-                Rect handleBounds = new Rect(
-                        handleCenterX - mHandleWidth / 2,
-                        handleCenterY - mHandleHeight / 2,
-                        handleCenterX + mHandleWidth / 2,
-                        handleCenterY + mHandleHeight / 2);
-                outline.setRoundRect(handleBounds, handleRadius);
+                final int handleTotalWidth = mDotSize * 3 + mDotSpacing * 2;
+                final int handleLeft = handleCenterX - handleTotalWidth / 2;
+                final int handleTop = handleCenterY - mDotSize / 2;
+                final int handleBottom = handleTop + mDotSize;
+                RectF dot1 = new RectF(
+                        handleLeft, handleTop,
+                        handleLeft + mDotSize, handleBottom);
+                RectF dot2 = new RectF(
+                        dot1.right + mDotSpacing, handleTop,
+                        dot1.right + mDotSpacing + mDotSize, handleBottom
+                );
+                RectF dot3 = new RectF(
+                        dot2.right + mDotSpacing, handleTop,
+                        dot2.right + mDotSpacing + mDotSize, handleBottom
+                );
+                mPath.reset();
+                mPath.addOval(dot1, Path.Direction.CW);
+                mPath.addOval(dot2, Path.Direction.CW);
+                mPath.addOval(dot3, Path.Direction.CW);
+                outline.setPath(mPath);
             }
         });
     }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java
index d20b33e..8ead18b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java
@@ -24,6 +24,7 @@
 import android.graphics.Rect;
 import android.graphics.Region;
 import android.graphics.drawable.ColorDrawable;
+import android.view.TouchDelegate;
 import android.view.View;
 import android.view.ViewTreeObserver;
 import android.widget.FrameLayout;
@@ -68,6 +69,10 @@
     private final Region mTouchableRegion = new Region();
     private final Rect mTempRect = new Rect();
 
+    // Used to ensure touch target size for the menu shown on a bubble expanded view
+    private TouchDelegate mHandleTouchDelegate;
+    private final Rect mHandleTouchBounds = new Rect();
+
     public BubbleBarLayerView(Context context, BubbleController controller) {
         super(context);
         mBubbleController = controller;
@@ -164,7 +169,17 @@
 
         mIsExpanded = true;
         mBubbleController.getSysuiProxy().onStackExpandChanged(true);
-        mAnimationHelper.animateExpansion(mExpandedBubble);
+        mAnimationHelper.animateExpansion(mExpandedBubble, () -> {
+            if (mExpandedView == null) return;
+            // Touch delegate for the menu
+            BubbleBarHandleView view = mExpandedView.getHandleView();
+            view.getBoundsOnScreen(mHandleTouchBounds);
+            mHandleTouchBounds.top -= mPositioner.getBubblePaddingTop();
+            mHandleTouchDelegate = new TouchDelegate(mHandleTouchBounds,
+                    mExpandedView.getHandleView());
+            setTouchDelegate(mHandleTouchDelegate);
+        });
+
         showScrim(true);
     }
 
@@ -175,6 +190,7 @@
         mAnimationHelper.animateCollapse(() -> removeView(viewToRemove));
         mBubbleController.getSysuiProxy().onStackExpandChanged(false);
         mExpandedView = null;
+        setTouchDelegate(null);
         showScrim(false);
     }
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerView.java
index 262d487..2dbc444 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerView.java
@@ -434,7 +434,7 @@
                 "Set divider bar %s from %s", interactive ? "interactive" : "non-interactive",
                 from);
         mInteractive = interactive;
-        if (!mInteractive && mMoving) {
+        if (!mInteractive && hideHandle && mMoving) {
             final int position = mSplitLayout.getDividePosition();
             mSplitLayout.flingDividePosition(
                     mLastDraggingPosition,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitDecorManager.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitDecorManager.java
index a9ccdf6..2b10377 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitDecorManager.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitDecorManager.java
@@ -323,7 +323,11 @@
             }
         }
         if (mShown) {
-            fadeOutDecor(()-> animFinishedCallback.accept(true));
+            fadeOutDecor(()-> {
+                if (mRunningAnimationCount == 0 && animFinishedCallback != null) {
+                    animFinishedCallback.accept(true);
+                }
+            });
         } else {
             // Decor surface is hidden so release it directly.
             releaseDecor(t);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java
index f70d3ae..e8fa638 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java
@@ -593,9 +593,6 @@
     void flingDividePosition(int from, int to, int duration,
             @Nullable Runnable flingFinishedCallback) {
         if (from == to) {
-            // No animation run, still callback to stop resizing.
-            mSplitLayoutHandler.onLayoutSizeChanged(this);
-
             if (flingFinishedCallback != null) {
                 flingFinishedCallback.run();
             }
@@ -773,15 +770,13 @@
             ActivityManager.RunningTaskInfo task1, ActivityManager.RunningTaskInfo task2) {
         boolean boundsChanged = false;
         if (!mBounds1.equals(mWinBounds1) || !task1.token.equals(mWinToken1)) {
-            wct.setBounds(task1.token, mBounds1);
-            wct.setSmallestScreenWidthDp(task1.token, getSmallestWidthDp(mBounds1));
+            setTaskBounds(wct, task1, mBounds1);
             mWinBounds1.set(mBounds1);
             mWinToken1 = task1.token;
             boundsChanged = true;
         }
         if (!mBounds2.equals(mWinBounds2) || !task2.token.equals(mWinToken2)) {
-            wct.setBounds(task2.token, mBounds2);
-            wct.setSmallestScreenWidthDp(task2.token, getSmallestWidthDp(mBounds2));
+            setTaskBounds(wct, task2, mBounds2);
             mWinBounds2.set(mBounds2);
             mWinToken2 = task2.token;
             boundsChanged = true;
@@ -789,6 +784,13 @@
         return boundsChanged;
     }
 
+    /** Set bounds to the {@link WindowContainerTransaction} for single task. */
+    public void setTaskBounds(WindowContainerTransaction wct,
+            ActivityManager.RunningTaskInfo task, Rect bounds) {
+        wct.setBounds(task.token, bounds);
+        wct.setSmallestScreenWidthDp(task.token, getSmallestWidthDp(bounds));
+    }
+
     private int getSmallestWidthDp(Rect bounds) {
         mTempRect.set(bounds);
         mTempRect.inset(getDisplayStableInsets(mContext));
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitScreenUtils.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitScreenUtils.java
index 0289da9..d7ea1c0 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitScreenUtils.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitScreenUtils.java
@@ -89,4 +89,9 @@
             int userId1, int userId2) {
         return (packageName1 != null && packageName1.equals(packageName2)) && (userId1 == userId2);
     }
+
+    /** Generates a common log message for split screen failures */
+    public static String splitFailureMessage(String caller, String reason) {
+        return "(" + caller + ") Splitscreen aborted: " + reason;
+    }
 }
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 4fda4b7..6d14440 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
@@ -63,6 +63,7 @@
 import com.android.wm.shell.transition.Transitions
 import com.android.wm.shell.util.KtProtoLog
 import com.android.wm.shell.windowdecor.DesktopModeWindowDecoration
+import com.android.wm.shell.windowdecor.MoveToDesktopAnimator
 import java.io.PrintWriter
 import java.util.concurrent.Executor
 import java.util.function.Consumer
@@ -204,7 +205,11 @@
      * Moves a single task to freeform and sets the taskBounds to the passed in bounds,
      * startBounds
      */
-    fun moveToFreeform(taskInfo: RunningTaskInfo, startBounds: Rect) {
+    fun moveToFreeform(
+            taskInfo: RunningTaskInfo,
+            startBounds: Rect,
+            dragToDesktopValueAnimator: MoveToDesktopAnimator
+    ) {
         KtProtoLog.v(
             WM_SHELL_DESKTOP_MODE,
             "DesktopTasksController: moveToFreeform with bounds taskId=%d",
@@ -216,8 +221,8 @@
         wct.setBounds(taskInfo.token, startBounds)
 
         if (Transitions.ENABLE_SHELL_TRANSITIONS) {
-            enterDesktopTaskTransitionHandler.startTransition(
-                    Transitions.TRANSIT_ENTER_FREEFORM, wct, mOnAnimationFinishedCallback)
+            enterDesktopTaskTransitionHandler.startMoveToFreeformAnimation(wct,
+                    dragToDesktopValueAnimator, mOnAnimationFinishedCallback)
         } else {
             shellTaskOrganizer.applyTransaction(wct)
         }
@@ -270,7 +275,7 @@
      * Move a task to fullscreen after being dragged from fullscreen and released back into
      * status bar area
      */
-    fun cancelMoveToFreeform(task: RunningTaskInfo, position: Point) {
+    fun cancelMoveToFreeform(task: RunningTaskInfo, moveToDesktopAnimator: MoveToDesktopAnimator) {
         KtProtoLog.v(
             WM_SHELL_DESKTOP_MODE,
             "DesktopTasksController: cancelMoveToFreeform taskId=%d",
@@ -280,13 +285,13 @@
         wct.setBounds(task.token, null)
 
         if (Transitions.ENABLE_SHELL_TRANSITIONS) {
-            enterDesktopTaskTransitionHandler.startCancelMoveToDesktopMode(
-                wct, position) { t ->
+            enterDesktopTaskTransitionHandler.startCancelMoveToDesktopMode(wct,
+                    moveToDesktopAnimator) { t ->
                 val callbackWCT = WindowContainerTransaction()
                 visualIndicator?.releaseVisualIndicator(t)
                 visualIndicator = null
                 addMoveToFullscreenChanges(callbackWCT, task)
-                shellTaskOrganizer.applyTransaction(callbackWCT)
+                transitions.startTransition(TRANSIT_CHANGE, callbackWCT, null /* handler */)
             }
         } else {
             addMoveToFullscreenChanges(wct, task)
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/EnterDesktopTaskTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/EnterDesktopTaskTransitionHandler.java
index 3e175f3..650cac5 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/EnterDesktopTaskTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/EnterDesktopTaskTransitionHandler.java
@@ -22,9 +22,10 @@
 import android.animation.AnimatorListenerAdapter;
 import android.animation.ValueAnimator;
 import android.app.ActivityManager;
-import android.graphics.Point;
+import android.graphics.PointF;
 import android.graphics.Rect;
 import android.os.IBinder;
+import android.util.Slog;
 import android.view.SurfaceControl;
 import android.view.WindowManager;
 import android.window.TransitionInfo;
@@ -35,6 +36,7 @@
 import androidx.annotation.Nullable;
 
 import com.android.wm.shell.transition.Transitions;
+import com.android.wm.shell.windowdecor.MoveToDesktopAnimator;
 
 import java.util.ArrayList;
 import java.util.List;
@@ -47,18 +49,17 @@
  */
 public class EnterDesktopTaskTransitionHandler implements Transitions.TransitionHandler {
 
+    private static final String TAG = "EnterDesktopTaskTransitionHandler";
     private final Transitions mTransitions;
     private final Supplier<SurfaceControl.Transaction> mTransactionSupplier;
 
-    // The size of the screen during drag relative to the fullscreen size
-    public static final float DRAG_FREEFORM_SCALE = 0.4f;
     // The size of the screen after drag relative to the fullscreen size
     public static final float FINAL_FREEFORM_SCALE = 0.6f;
     public static final int FREEFORM_ANIMATION_DURATION = 336;
 
     private final List<IBinder> mPendingTransitionTokens = new ArrayList<>();
-    private Point mPosition;
     private Consumer<SurfaceControl.Transaction> mOnAnimationFinishedCallback;
+    private MoveToDesktopAnimator mMoveToDesktopAnimator;
 
     public EnterDesktopTaskTransitionHandler(
             Transitions transitions) {
@@ -87,15 +88,30 @@
     }
 
     /**
+     * Starts Transition of type TRANSIT_ENTER_FREEFORM
+     * @param wct WindowContainerTransaction for transition
+     * @param moveToDesktopAnimator Animator that shrinks and positions task during two part move
+     *                              to desktop animation
+     * @param onAnimationEndCallback to be called after animation
+     */
+    public void startMoveToFreeformAnimation(@NonNull WindowContainerTransaction wct,
+            @NonNull MoveToDesktopAnimator moveToDesktopAnimator,
+            Consumer<SurfaceControl.Transaction> onAnimationEndCallback) {
+        mMoveToDesktopAnimator = moveToDesktopAnimator;
+        startTransition(Transitions.TRANSIT_ENTER_FREEFORM, wct, onAnimationEndCallback);
+    }
+
+    /**
      * Starts Transition of type TRANSIT_CANCEL_ENTERING_DESKTOP_MODE
      * @param wct WindowContainerTransaction for transition
-     * @param position Position of task when transition is triggered
+     * @param moveToDesktopAnimator Animator that shrinks and positions task during two part move
+     *                              to desktop animation
      * @param onAnimationEndCallback to be called after animation
      */
     public void startCancelMoveToDesktopMode(@NonNull WindowContainerTransaction wct,
-            Point position,
+            MoveToDesktopAnimator moveToDesktopAnimator,
             Consumer<SurfaceControl.Transaction> onAnimationEndCallback) {
-        mPosition = position;
+        mMoveToDesktopAnimator = moveToDesktopAnimator;
         startTransition(Transitions.TRANSIT_CANCEL_ENTERING_DESKTOP_MODE, wct,
                 onAnimationEndCallback);
     }
@@ -145,9 +161,23 @@
             // to null and we don't require an animation
             final SurfaceControl sc = change.getLeash();
             startT.setWindowCrop(sc, null);
+
+            if (mMoveToDesktopAnimator == null
+                    || mMoveToDesktopAnimator.getTaskId() != change.getTaskInfo().taskId) {
+                Slog.e(TAG, "No animator available for this transition");
+                return false;
+            }
+
+            // Calculate and set position of the task
+            final PointF position = mMoveToDesktopAnimator.getPosition();
+            startT.setPosition(sc, position.x, position.y);
+            finishT.setPosition(sc, position.x, position.y);
+
             startT.apply();
+
             mTransitions.getMainExecutor().execute(
                     () -> finishCallback.onTransitionFinished(null, null));
+
             return true;
         }
 
@@ -162,12 +192,18 @@
                     endBounds.height());
             startT.apply();
 
+            // End the animation that shrinks the window when task is first dragged from fullscreen
+            if (mMoveToDesktopAnimator != null) {
+                mMoveToDesktopAnimator.endAnimator();
+            }
+
             // We want to find the scale of the current bounds relative to the end bounds. The
             // task is currently scaled to DRAG_FREEFORM_SCALE and the final bounds will be
             // scaled to FINAL_FREEFORM_SCALE. So, it is scaled to
             // DRAG_FREEFORM_SCALE / FINAL_FREEFORM_SCALE relative to the freeform bounds
             final ValueAnimator animator =
-                    ValueAnimator.ofFloat(DRAG_FREEFORM_SCALE / FINAL_FREEFORM_SCALE, 1f);
+                    ValueAnimator.ofFloat(
+                            MoveToDesktopAnimator.DRAG_FREEFORM_SCALE / FINAL_FREEFORM_SCALE, 1f);
             animator.setDuration(FREEFORM_ANIMATION_DURATION);
             final SurfaceControl.Transaction t = mTransactionSupplier.get();
             animator.addUpdateListener(animation -> {
@@ -199,8 +235,7 @@
         }
 
         if (type == Transitions.TRANSIT_CANCEL_ENTERING_DESKTOP_MODE
-                && taskInfo.getWindowingMode() == WINDOWING_MODE_FREEFORM
-                && mPosition != null) {
+                && taskInfo.getWindowingMode() == WINDOWING_MODE_FREEFORM) {
             // This Transition animates a task to fullscreen after being dragged from the status
             // bar and then released back into the status bar area
             final SurfaceControl sc = change.getLeash();
@@ -210,13 +245,27 @@
                     .setWindowCrop(sc, endBounds.width(), endBounds.height())
                     .apply();
 
+            if (mMoveToDesktopAnimator == null
+                    || mMoveToDesktopAnimator.getTaskId() != change.getTaskInfo().taskId) {
+                Slog.e(TAG, "No animator available for this transition");
+                return false;
+            }
+
+            // End the animation that shrinks the window when task is first dragged from fullscreen
+            mMoveToDesktopAnimator.endAnimator();
+
             final ValueAnimator animator = new ValueAnimator();
-            animator.setFloatValues(DRAG_FREEFORM_SCALE, 1f);
+            animator.setFloatValues(MoveToDesktopAnimator.DRAG_FREEFORM_SCALE, 1f);
             animator.setDuration(FREEFORM_ANIMATION_DURATION);
             final SurfaceControl.Transaction t = mTransactionSupplier.get();
+
+            // Get position of the task
+            final float x = mMoveToDesktopAnimator.getPosition().x;
+            final float y = mMoveToDesktopAnimator.getPosition().y;
+
             animator.addUpdateListener(animation -> {
                 final float scale = (float) animation.getAnimatedValue();
-                t.setPosition(sc, mPosition.x * (1 - scale), mPosition.y * (1 - scale))
+                t.setPosition(sc, x * (1 - scale), y * (1 - scale))
                         .setScale(sc, scale, scale)
                         .show(sc)
                         .apply();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java
index 24aaa9b..73eb62a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java
@@ -339,19 +339,36 @@
         }
         // This means an expand happened before enter-pip finished and we are now "merging" a
         // no-op transition that happens to match our exit-pip.
+        // Or that the keyguard is up and preventing the transition from applying, in which case we
+        // want to manually reset pip. (b/283783868)
         boolean cancelled = false;
         if (mPipAnimationController.getCurrentAnimator() != null) {
             mPipAnimationController.getCurrentAnimator().cancel();
+            mPipAnimationController.resetAnimatorState();
             cancelled = true;
         }
+
         // Unset exitTransition AFTER cancel so that finishResize knows we are merging.
         mExitTransition = null;
-        if (!cancelled || aborted) return;
+        if (!cancelled) return;
         final ActivityManager.RunningTaskInfo taskInfo = mPipOrganizer.getTaskInfo();
         if (taskInfo != null) {
-            startExpandAnimation(taskInfo, mPipOrganizer.getSurfaceControl(),
-                    mPipBoundsState.getBounds(), mPipBoundsState.getBounds(),
-                    new Rect(mExitDestinationBounds), Surface.ROTATION_0, null /* startT */);
+            if (aborted) {
+                // keyguard case - the transition got aborted, so we want to reset state and
+                // windowing mode before reapplying the resize transaction
+                sendOnPipTransitionFinished(TRANSITION_DIRECTION_LEAVE_PIP);
+                mPipOrganizer.onExitPipFinished(taskInfo);
+
+                WindowContainerTransaction wct = new WindowContainerTransaction();
+                mPipOrganizer.applyWindowingModeChangeOnExit(wct, TRANSITION_DIRECTION_LEAVE_PIP);
+                wct.setBounds(taskInfo.token, null);
+                mPipOrganizer.applyFinishBoundsResize(wct, TRANSITION_DIRECTION_LEAVE_PIP, false);
+            } else {
+                // merge case
+                startExpandAnimation(taskInfo, mPipOrganizer.getSurfaceControl(),
+                        mPipBoundsState.getBounds(), mPipBoundsState.getBounds(),
+                        new Rect(mExitDestinationBounds), Surface.ROTATION_0, null /* startT */);
+            }
         }
         mExitDestinationBounds.setEmpty();
         mCurrentPipTaskToken = null;
@@ -567,7 +584,16 @@
                 mPipBoundsState.getDisplayBounds());
         mFinishCallback = (wct, wctCB) -> {
             mPipOrganizer.onExitPipFinished(taskInfo);
-            if (!Transitions.SHELL_TRANSITIONS_ROTATION && toFullscreen) {
+
+            // TODO(b/286346098): remove the OPEN app flicker completely
+            // not checking if we go to fullscreen helps avoid getting pip into an inconsistent
+            // state after the flicker occurs. This is a temp solution until flicker is removed.
+            if (!Transitions.SHELL_TRANSITIONS_ROTATION) {
+                // will help to debug the case when we are not exiting to fullscreen
+                if (!toFullscreen) {
+                    ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+                            "%s: startExitAnimation() not exiting to fullscreen", TAG);
+                }
                 wct = wct != null ? wct : new WindowContainerTransaction();
                 wct.setBounds(pipTaskToken, null);
                 mPipOrganizer.applyWindowingModeChangeOnExit(wct, TRANSITION_DIRECTION_LEAVE_PIP);
@@ -831,7 +857,7 @@
         }
 
         final Rect destinationBounds = mPipBoundsAlgorithm.getEntryDestinationBounds();
-        final Rect currentBounds = taskInfo.configuration.windowConfiguration.getBounds();
+        final Rect currentBounds = pipChange.getStartAbsBounds();
         int rotationDelta = deltaRotation(startRotation, endRotation);
         Rect sourceHintRect = PipBoundsAlgorithm.getValidSourceHintRect(
                 taskInfo.pictureInPictureParams, currentBounds, destinationBounds);
@@ -1050,7 +1076,7 @@
         // When the PIP window is visible and being a part of the transition, such as display
         // rotation, we need to update its bounds and rounded corner.
         final SurfaceControl leash = pipChange.getLeash();
-        final Rect destBounds = mPipBoundsState.getBounds();
+        final Rect destBounds = mPipOrganizer.getCurrentOrAnimatingBounds();
         final boolean isInPip = mPipTransitionState.isInPip();
         mSurfaceTransactionHelper
                 .crop(startTransaction, leash, destBounds)
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java
index 843e5af..39b6675 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java
@@ -567,15 +567,18 @@
                         && taskInfo.configuration.windowConfiguration.isAlwaysOnTop()) {
                     // Tasks that are always on top (e.g. bubbles), will handle their own transition
                     // as they are on top of everything else. So cancel the merge here.
-                    cancel("task #" + taskInfo.taskId + " is always_on_top");
+                    cancel(false /* toHome */, false /* withScreenshots */,
+                            "task #" + taskInfo.taskId + " is always_on_top");
                     return;
                 }
                 final boolean isRootTask = taskInfo != null
                         && TransitionInfo.isIndependent(change, info);
+                final boolean isRecentsTask = mRecentsTask != null
+                        && mRecentsTask.equals(change.getContainer());
                 hasTaskChange = hasTaskChange || isRootTask;
                 final boolean isLeafTask = leafTaskFilter.test(change);
                 if (TransitionUtil.isOpeningType(change.getMode())) {
-                    if (mRecentsTask != null && mRecentsTask.equals(change.getContainer())) {
+                    if (isRecentsTask) {
                         recentsOpening = change;
                     } else if (isRootTask || isLeafTask) {
                         if (isLeafTask && taskInfo.topActivityType == ACTIVITY_TYPE_HOME) {
@@ -590,7 +593,7 @@
                         openingTaskIsLeafs.add(isLeafTask ? 1 : 0);
                     }
                 } else if (TransitionUtil.isClosingType(change.getMode())) {
-                    if (mRecentsTask != null && mRecentsTask.equals(change.getContainer())) {
+                    if (isRecentsTask) {
                         foundRecentsClosing = true;
                     } else if (isRootTask || isLeafTask) {
                         if (closingTasks == null) {
@@ -611,7 +614,7 @@
                     if (!TransitionUtil.isOrderOnly(change) && isLeafTask) {
                         hasChangingApp = true;
                     } else if (isLeafTask && taskInfo.topActivityType == ACTIVITY_TYPE_HOME
-                            && !mRecentsTask.equals(change.getContainer())) {
+                            && !isRecentsTask ) {
                         // Unless it is a 3p launcher. This means that the 3p launcher was already
                         // visible (eg. the "pausing" task is translucent over the 3p launcher).
                         // Treat it as if we are "re-opening" the 3p launcher.
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
index e294229..3669bce 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
@@ -31,6 +31,7 @@
 import static com.android.wm.shell.common.split.SplitScreenUtils.isValidToSplit;
 import static com.android.wm.shell.common.split.SplitScreenUtils.reverseSplitPosition;
 import static com.android.wm.shell.common.split.SplitScreenUtils.samePackage;
+import static com.android.wm.shell.common.split.SplitScreenUtils.splitFailureMessage;
 import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_TYPE_UNDEFINED;
 import static com.android.wm.shell.sysui.ShellSharedConstants.KEY_EXTRA_SHELL_SPLIT_SCREEN;
 import static com.android.wm.shell.transition.Transitions.ENABLE_SHELL_TRANSITIONS;
@@ -50,6 +51,7 @@
 import android.os.RemoteException;
 import android.os.UserHandle;
 import android.util.ArrayMap;
+import android.util.Log;
 import android.util.Slog;
 import android.view.IRemoteAnimationFinishedCallback;
 import android.view.IRemoteAnimationRunner;
@@ -551,6 +553,8 @@
             } else {
                 ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN,
                         "Cancel entering split as not supporting multi-instances");
+                Log.w(TAG, splitFailureMessage("startShortcut",
+                        "app package " + packageName + " does not support multi-instance"));
                 Toast.makeText(mContext, R.string.dock_multi_instances_not_supported_text,
                         Toast.LENGTH_SHORT).show();
                 return;
@@ -580,6 +584,8 @@
                 taskId = INVALID_TASK_ID;
                 ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN,
                         "Cancel entering split as not supporting multi-instances");
+                Log.w(TAG, splitFailureMessage("startShortcutAndTaskWithLegacyTransition",
+                        "app package " + packageName1 + " does not support multi-instance"));
                 Toast.makeText(mContext, R.string.dock_multi_instances_not_supported_text,
                         Toast.LENGTH_SHORT).show();
             }
@@ -612,6 +618,8 @@
                 taskId = INVALID_TASK_ID;
                 ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN,
                         "Cancel entering split as not supporting multi-instances");
+                Log.w(TAG, splitFailureMessage("startShortcutAndTask",
+                        "app package " + packageName1 + " does not support multi-instance"));
                 Toast.makeText(mContext, R.string.dock_multi_instances_not_supported_text,
                         Toast.LENGTH_SHORT).show();
             }
@@ -647,6 +655,8 @@
                 taskId = INVALID_TASK_ID;
                 ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN,
                         "Cancel entering split as not supporting multi-instances");
+                Log.w(TAG, splitFailureMessage("startIntentAndTaskWithLegacyTransition",
+                        "app package " + packageName1 + " does not support multi-instance"));
                 Toast.makeText(mContext, R.string.dock_multi_instances_not_supported_text,
                         Toast.LENGTH_SHORT).show();
             }
@@ -677,6 +687,8 @@
                 taskId = INVALID_TASK_ID;
                 ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN,
                         "Cancel entering split as not supporting multi-instances");
+                Log.w(TAG, splitFailureMessage("startIntentAndTask",
+                        "app package " + packageName1 + " does not support multi-instance"));
                 Toast.makeText(mContext, R.string.dock_multi_instances_not_supported_text,
                         Toast.LENGTH_SHORT).show();
             }
@@ -705,6 +717,8 @@
                 pendingIntent2 = null;
                 ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN,
                         "Cancel entering split as not supporting multi-instances");
+                Log.w(TAG, splitFailureMessage("startIntentsWithLegacyTransition",
+                        "app package " + packageName1 + " does not support multi-instance"));
                 Toast.makeText(mContext, R.string.dock_multi_instances_not_supported_text,
                         Toast.LENGTH_SHORT).show();
             }
@@ -734,6 +748,8 @@
                 pendingIntent2 = null;
                 ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN,
                         "Cancel entering split as not supporting multi-instances");
+                Log.w(TAG, splitFailureMessage("startIntents",
+                        "app package " + packageName1 + " does not support multi-instance"));
                 Toast.makeText(mContext, R.string.dock_multi_instances_not_supported_text,
                         Toast.LENGTH_SHORT).show();
             }
@@ -780,6 +796,8 @@
             } else {
                 ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN,
                         "Cancel entering split as not supporting multi-instances");
+                Log.w(TAG, splitFailureMessage("startIntent",
+                        "app package " + packageName1 + " does not support multi-instance"));
                 Toast.makeText(mContext, R.string.dock_multi_instances_not_supported_text,
                         Toast.LENGTH_SHORT).show();
                 return;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
index acc1c5e..6961dab 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
@@ -28,6 +28,7 @@
 import static android.view.RemoteAnimationTarget.MODE_OPENING;
 import static android.view.WindowManager.LayoutParams.TYPE_DOCK_DIVIDER;
 import static android.view.WindowManager.TRANSIT_CHANGE;
+import static android.view.WindowManager.TRANSIT_KEYGUARD_OCCLUDE;
 import static android.view.WindowManager.TRANSIT_TO_BACK;
 import static android.view.WindowManager.TRANSIT_TO_FRONT;
 import static android.view.WindowManager.transitTypeToString;
@@ -41,6 +42,7 @@
 import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_UNDEFINED;
 import static com.android.wm.shell.common.split.SplitScreenConstants.splitPositionToString;
 import static com.android.wm.shell.common.split.SplitScreenUtils.reverseSplitPosition;
+import static com.android.wm.shell.common.split.SplitScreenUtils.splitFailureMessage;
 import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_TYPE_MAIN;
 import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_TYPE_SIDE;
 import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_TYPE_UNDEFINED;
@@ -473,6 +475,8 @@
                 if (isEnteringSplit && mSideStage.getChildCount() == 0) {
                     mMainExecutor.execute(() -> exitSplitScreen(
                             null /* childrenToTop */, EXIT_REASON_UNKNOWN));
+                    Log.w(TAG, splitFailureMessage("startShortcut",
+                            "side stage was not populated"));
                     mSplitUnsupportedToast.show();
                 }
 
@@ -560,6 +564,8 @@
                 if (isEnteringSplit && mSideStage.getChildCount() == 0) {
                     mMainExecutor.execute(() -> exitSplitScreen(
                             null /* childrenToTop */, EXIT_REASON_UNKNOWN));
+                    Log.w(TAG, splitFailureMessage("startIntentLegacy",
+                            "side stage was not populated"));
                     mSplitUnsupportedToast.show();
                 }
 
@@ -1090,6 +1096,8 @@
             mMainExecutor.execute(() ->
                     exitSplitScreen(mMainStage.getChildCount() == 0
                             ? mSideStage : mMainStage, EXIT_REASON_UNKNOWN));
+            Log.w(TAG, splitFailureMessage("onRemoteAnimationFinishedOrCancelled",
+                    "main or side stage was not populated."));
             mSplitUnsupportedToast.show();
         } else {
             mSyncQueue.queue(evictWct);
@@ -1109,6 +1117,8 @@
         if (mMainStage.getChildCount() == 0 || mSideStage.getChildCount() == 0) {
             mMainExecutor.execute(() -> exitSplitScreen(mMainStage.getChildCount() == 0
                     ? mSideStage : mMainStage, EXIT_REASON_UNKNOWN));
+            Log.w(TAG, splitFailureMessage("onRemoteAnimationFinished",
+                    "main or side stage was not populated"));
             mSplitUnsupportedToast.show();
             return;
         }
@@ -1293,20 +1303,12 @@
         final boolean mainStageVisible = mMainStage.mRootTaskInfo.isVisible;
         final boolean oneStageVisible =
                 mMainStage.mRootTaskInfo.isVisible != mSideStage.mRootTaskInfo.isVisible;
-        if (oneStageVisible) {
+        if (oneStageVisible && !ENABLE_SHELL_TRANSITIONS) {
             // Dismiss split because there's show-when-locked activity showing on top of keyguard.
             // Also make sure the task contains show-when-locked activity remains on top after split
             // dismissed.
-            if (!ENABLE_SHELL_TRANSITIONS) {
-                final StageTaskListener toTop = mainStageVisible ? mMainStage : mSideStage;
-                exitSplitScreen(toTop, EXIT_REASON_SCREEN_LOCKED_SHOW_ON_TOP);
-            } else {
-                final int dismissTop = mainStageVisible ? STAGE_TYPE_MAIN : STAGE_TYPE_SIDE;
-                final WindowContainerTransaction wct = new WindowContainerTransaction();
-                prepareExitSplitScreen(dismissTop, wct);
-                mSplitTransitions.startDismissTransition(wct, this, dismissTop,
-                        EXIT_REASON_SCREEN_LOCKED_SHOW_ON_TOP);
-            }
+            final StageTaskListener toTop = mainStageVisible ? mMainStage : mSideStage;
+            exitSplitScreen(toTop, EXIT_REASON_SCREEN_LOCKED_SHOW_ON_TOP);
         }
     }
 
@@ -1561,6 +1563,8 @@
             // split bounds.
             wct.setSmallestScreenWidthDp(mMainStage.mRootTaskInfo.token,
                     SMALLEST_SCREEN_WIDTH_DP_UNDEFINED);
+            mSplitLayout.getInvisibleBounds(mTempRect1);
+            mSplitLayout.setTaskBounds(wct, mSideStage.mRootTaskInfo, mTempRect1);
         }
         wct.reorder(mRootTaskInfo.token, true);
         setRootForceTranslucent(false, wct);
@@ -2376,6 +2380,15 @@
                     // so appends operations to exit split.
                     prepareExitSplitScreen(STAGE_TYPE_UNDEFINED, out);
                 }
+            } else if (type == TRANSIT_KEYGUARD_OCCLUDE && triggerTask.topActivity != null
+                    && isSplitScreenVisible()) {
+                // Split include show when lock activity case, check the top activity under which
+                // stage and move it to the top.
+                int top = triggerTask.topActivity.equals(mMainStage.mRootTaskInfo.topActivity)
+                        ? STAGE_TYPE_MAIN : STAGE_TYPE_SIDE;
+                prepareExitSplitScreen(top, out);
+                mSplitTransitions.setDismissTransition(transition, top,
+                        EXIT_REASON_SCREEN_LOCKED_SHOW_ON_TOP);
             }
 
             // When split in the background, it should be only opening/dismissing transition and
@@ -2704,12 +2717,13 @@
             }
         } else {
             if (mainChild == null || sideChild == null) {
-                Log.w(TAG, "Launched 2 tasks in split, but didn't receive"
-                        + " 2 tasks in transition. Possibly one of them failed to launch");
                 final int dismissTop = mainChild != null ? STAGE_TYPE_MAIN :
                         (sideChild != null ? STAGE_TYPE_SIDE : STAGE_TYPE_UNDEFINED);
                 mSplitTransitions.mPendingEnter.cancel(
                         (cancelWct, cancelT) -> prepareExitSplitScreen(dismissTop, cancelWct));
+                Log.w(TAG, splitFailureMessage("startPendingEnterAnimation",
+                        "launched 2 tasks in split, but didn't receive "
+                        + "2 tasks in transition. Possibly one of them failed to launch"));
                 mSplitUnsupportedToast.show();
                 return true;
             }
@@ -2853,18 +2867,24 @@
             }
         }
 
+        final ArrayMap<Integer, SurfaceControl> dismissingTasks = new ArrayMap<>();
+        for (int i = info.getChanges().size() - 1; i >= 0; --i) {
+            final TransitionInfo.Change change = info.getChanges().get(i);
+            final ActivityManager.RunningTaskInfo taskInfo = change.getTaskInfo();
+            if (taskInfo == null) continue;
+            if (getStageOfTask(taskInfo) != null
+                    || getSplitItemPosition(change.getLastParent()) != SPLIT_POSITION_UNDEFINED) {
+                dismissingTasks.put(taskInfo.taskId, change.getLeash());
+            }
+        }
+
+
         if (shouldBreakPairedTaskInRecents(dismissReason)) {
             // Notify recents if we are exiting in a way that breaks the pair, and disable further
             // updates to splits in the recents until we enter split again
             mRecentTasks.ifPresent(recentTasks -> {
-                for (int i = info.getChanges().size() - 1; i >= 0; --i) {
-                    final TransitionInfo.Change change = info.getChanges().get(i);
-                    final ActivityManager.RunningTaskInfo taskInfo = change.getTaskInfo();
-                    if (taskInfo != null && (getStageOfTask(taskInfo) != null
-                            || getSplitItemPosition(change.getLastParent())
-                            != SPLIT_POSITION_UNDEFINED)) {
-                        recentTasks.removeSplitPair(taskInfo.taskId);
-                    }
+                for (int i = dismissingTasks.keySet().size() - 1; i >= 0; --i) {
+                    recentTasks.removeSplitPair(dismissingTasks.keyAt(i));
                 }
             });
         }
@@ -2882,6 +2902,10 @@
             t.hide(toStage == STAGE_TYPE_MAIN ? mSideStage.mRootLeash : mMainStage.mRootLeash);
             t.setPosition(toStage == STAGE_TYPE_MAIN
                     ? mMainStage.mRootLeash : mSideStage.mRootLeash, 0, 0);
+        } else {
+            for (int i = dismissingTasks.keySet().size() - 1; i >= 0; --i) {
+                finishT.hide(dismissingTasks.valueAt(i));
+            }
         }
 
         if (toStage == STAGE_TYPE_UNDEFINED) {
@@ -2891,7 +2915,7 @@
         }
 
         // Hide divider and dim layer on transition finished.
-        setDividerVisibility(false, finishT);
+        setDividerVisibility(false, t);
         finishT.hide(mMainStage.mDimLayer);
         finishT.hide(mSideStage.mDimLayer);
     }
@@ -3159,7 +3183,7 @@
         }
 
         @Override
-        public void onNoLongerSupportMultiWindow() {
+        public void onNoLongerSupportMultiWindow(ActivityManager.RunningTaskInfo taskInfo) {
             if (mMainStage.isActive()) {
                 final boolean isMainStage = mMainStageListener == this;
                 if (!ENABLE_SHELL_TRANSITIONS) {
@@ -3174,6 +3198,9 @@
                 prepareExitSplitScreen(stageType, wct);
                 mSplitTransitions.startDismissTransition(wct, StageCoordinator.this, stageType,
                         EXIT_REASON_APP_DOES_NOT_SUPPORT_MULTIWINDOW);
+                Log.w(TAG, splitFailureMessage("onNoLongerSupportMultiWindow",
+                        "app package " + taskInfo.baseActivity.getPackageName()
+                        + " does not support splitscreen, or is a controlled activity type"));
                 mSplitUnsupportedToast.show();
             }
         }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java
index 3ef4f02..e2c55e4 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java
@@ -81,7 +81,7 @@
 
         void onRootTaskVanished();
 
-        void onNoLongerSupportMultiWindow();
+        void onNoLongerSupportMultiWindow(ActivityManager.RunningTaskInfo taskInfo);
     }
 
     private final Context mContext;
@@ -226,7 +226,7 @@
                     taskInfo.getWindowingMode())) {
                 // Leave split screen if the task no longer supports multi window or have
                 // uncontrolled task.
-                mCallbacks.onNoLongerSupportMultiWindow();
+                mCallbacks.onNoLongerSupportMultiWindow(taskInfo);
                 return;
             }
             mChildrenTaskInfo.put(taskInfo.taskId, taskInfo);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/TaskSnapshotWindow.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/TaskSnapshotWindow.java
index c964df1..c2f15f6 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/TaskSnapshotWindow.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/TaskSnapshotWindow.java
@@ -16,6 +16,7 @@
 
 package com.android.wm.shell.startingsurface;
 
+import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
 import static android.graphics.Color.WHITE;
 import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER;
 import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_STARTING;
@@ -77,6 +78,13 @@
             @NonNull Runnable clearWindowHandler) {
         final ActivityManager.RunningTaskInfo runningTaskInfo = info.taskInfo;
         final int taskId = runningTaskInfo.taskId;
+
+        // if we're in PIP we don't want to create the snapshot
+        if (runningTaskInfo.getWindowingMode() == WINDOWING_MODE_PINNED) {
+            ProtoLog.v(ShellProtoLogGroup.WM_SHELL_STARTING_WINDOW,
+                    "did not create taskSnapshot due to being in PIP");
+            return null;
+        }
         ProtoLog.v(ShellProtoLogGroup.WM_SHELL_STARTING_WINDOW,
                 "create taskSnapshot surface for task: %d", taskId);
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskView.java
index 4faa929..0d77a2e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskView.java
@@ -26,6 +26,7 @@
 import android.content.Intent;
 import android.content.pm.LauncherApps;
 import android.content.pm.ShortcutInfo;
+import android.graphics.Insets;
 import android.graphics.Rect;
 import android.graphics.Region;
 import android.view.SurfaceControl;
@@ -69,8 +70,10 @@
     private final Rect mTmpRect = new Rect();
     private final Rect mTmpRootRect = new Rect();
     private final int[] mTmpLocation = new int[2];
+    private final Rect mBoundsOnScreen = new Rect();
     private final TaskViewTaskController mTaskViewTaskController;
     private Region mObscuredTouchRegion;
+    private Insets mCaptionInsets;
 
     public TaskView(Context context, TaskViewTaskController taskViewTaskController) {
         super(context, null, 0, 0, true /* disableBackgroundLayer */);
@@ -169,6 +172,25 @@
     }
 
     /**
+     * Sets a region of the task to inset to allow for a caption bar. Currently only top insets
+     * are supported.
+     * <p>
+     * This region will be factored in as an area of taskview that is not touchable activity
+     * content (i.e. you don't need to additionally set {@link #setObscuredTouchRect(Rect)} for
+     * the caption area).
+     *
+     * @param captionInsets the insets to apply to task view.
+     */
+    public void setCaptionInsets(Insets captionInsets) {
+        mCaptionInsets = captionInsets;
+        if (captionInsets == null) {
+            // If captions are null we can set them now; otherwise they'll get set in
+            // onComputeInternalInsets.
+            mTaskViewTaskController.setCaptionInsets(null);
+        }
+    }
+
+    /**
      * Call when view position or size has changed. Do not call when animating.
      */
     public void onLocationChanged() {
@@ -230,6 +252,15 @@
         getLocationInWindow(mTmpLocation);
         mTmpRect.set(mTmpLocation[0], mTmpLocation[1],
                 mTmpLocation[0] + getWidth(), mTmpLocation[1] + getHeight());
+        if (mCaptionInsets != null) {
+            mTmpRect.inset(mCaptionInsets);
+            getBoundsOnScreen(mBoundsOnScreen);
+            mTaskViewTaskController.setCaptionInsets(new Rect(
+                    mBoundsOnScreen.left,
+                    mBoundsOnScreen.top,
+                    mBoundsOnScreen.right + getWidth(),
+                    mBoundsOnScreen.top + mCaptionInsets.top));
+        }
         inoutInfo.touchableRegion.op(mTmpRect, Region.Op.DIFFERENCE);
 
         if (mObscuredTouchRegion != null) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTaskController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTaskController.java
index 163cf50..064af04 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTaskController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTaskController.java
@@ -33,6 +33,7 @@
 import android.util.CloseGuard;
 import android.util.Slog;
 import android.view.SurfaceControl;
+import android.view.WindowInsets;
 import android.window.WindowContainerToken;
 import android.window.WindowContainerTransaction;
 
@@ -82,6 +83,10 @@
     private TaskView.Listener mListener;
     private Executor mListenerExecutor;
 
+    /** Used to inset the activity content to allow space for a caption bar. */
+    private final Binder mCaptionInsetsOwner = new Binder();
+    private Rect mCaptionInsets;
+
     public TaskViewTaskController(Context context, ShellTaskOrganizer organizer,
             TaskViewTransitions taskViewTransitions, SyncTransactionQueue syncQueue) {
         mContext = context;
@@ -436,6 +441,32 @@
         mTaskViewTransitions.closeTaskView(wct, this);
     }
 
+    /**
+     * Sets a region of the task to inset to allow for a caption bar.
+     *
+     * @param captionInsets the rect for the insets in screen coordinates.
+     */
+    void setCaptionInsets(Rect captionInsets) {
+        if (mCaptionInsets != null && mCaptionInsets.equals(captionInsets)) {
+            return;
+        }
+        mCaptionInsets = captionInsets;
+        applyCaptionInsetsIfNeeded();
+    }
+
+    void applyCaptionInsetsIfNeeded() {
+        if (mTaskToken == null) return;
+        WindowContainerTransaction wct = new WindowContainerTransaction();
+        if (mCaptionInsets != null) {
+            wct.addInsetsSource(mTaskToken, mCaptionInsetsOwner, 0,
+                    WindowInsets.Type.captionBar(), mCaptionInsets);
+        } else {
+            wct.removeInsetsSource(mTaskToken, mCaptionInsetsOwner, 0,
+                    WindowInsets.Type.captionBar());
+        }
+        mTaskOrganizer.applyTransaction(wct);
+    }
+
     /** Should be called when the client surface is destroyed. */
     public void surfaceDestroyed() {
         mSurfaceCreated = false;
@@ -564,6 +595,7 @@
             mTaskViewTransitions.updateBoundsState(this, boundsOnScreen);
             mTaskViewTransitions.updateVisibilityState(this, true /* visible */);
             wct.setBounds(mTaskToken, boundsOnScreen);
+            applyCaptionInsetsIfNeeded();
         } else {
             // The surface has already been destroyed before the task has appeared,
             // so go ahead and hide the task entirely
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTransitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTransitions.java
index 5baf2e3..16f0e39 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTransitions.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTransitions.java
@@ -202,15 +202,10 @@
         if (taskView == null) return null;
         // Opening types should all be initiated by shell
         if (!TransitionUtil.isClosingType(request.getType())) return null;
-        PendingTransition pending = findPendingCloseTransition(taskView);
-        if (pending == null) {
-            pending = new PendingTransition(request.getType(), null, taskView, null /* cookie */);
-        }
-        if (pending.mClaimed != null) {
-            throw new IllegalStateException("Task is closing in 2 collecting transitions?"
-                    + " This state doesn't make sense");
-        }
+        PendingTransition pending = new PendingTransition(request.getType(), null,
+                taskView, null /* cookie */);
         pending.mClaimed = transition;
+        mPending.add(pending);
         return new WindowContainerTransaction();
     }
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java
index a28ce55..d9edde1 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java
@@ -25,6 +25,7 @@
 
 import static com.android.wm.shell.common.split.SplitScreenConstants.FLAG_IS_DIVIDER_BAR;
 import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_UNDEFINED;
+import static com.android.wm.shell.pip.PipAnimationController.ANIM_TYPE_ALPHA;
 import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_TYPE_UNDEFINED;
 import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_CHILD_TASK_ENTER_PIP;
 import static com.android.wm.shell.util.TransitionUtil.isOpeningType;
@@ -500,6 +501,7 @@
                 }
             }
 
+            mPipHandler.setEnterAnimationType(ANIM_TYPE_ALPHA);
             mPipHandler.startEnterAnimation(pipChange, startTransaction, finishTransaction,
                     finishCB);
             // Dispatch the rest of the transition normally. This will most-likely be taken by
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java
index b217bd3..ce81910 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java
@@ -145,7 +145,9 @@
                     mDisplay.getDisplayId(),
                     0 /* taskCornerRadius */,
                     mDecorationContainerSurface,
-                    mDragPositioningCallback);
+                    mDragPositioningCallback,
+                    mSurfaceControlBuilderSupplier,
+                    mSurfaceControlTransactionSupplier);
         }
 
         final int touchSlop = ViewConfiguration.get(mResult.mRootView.getContext())
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 7245bc9..14f2f9b 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
@@ -24,9 +24,9 @@
 
 import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT;
 import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT;
-import static com.android.wm.shell.desktopmode.EnterDesktopTaskTransitionHandler.DRAG_FREEFORM_SCALE;
 import static com.android.wm.shell.desktopmode.EnterDesktopTaskTransitionHandler.FINAL_FREEFORM_SCALE;
 import static com.android.wm.shell.desktopmode.EnterDesktopTaskTransitionHandler.FREEFORM_ANIMATION_DURATION;
+import static com.android.wm.shell.windowdecor.MoveToDesktopAnimator.DRAG_FREEFORM_SCALE;
 
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
@@ -112,9 +112,8 @@
 
     private SplitScreenController mSplitScreenController;
 
-    private ValueAnimator mDragToDesktopValueAnimator;
+    private MoveToDesktopAnimator mMoveToDesktopAnimator;
     private final Rect mDragToDesktopAnimationStartBounds = new Rect();
-    private boolean mDragToDesktopAnimationStarted;
 
     public DesktopModeWindowDecorViewModel(
             Context context,
@@ -233,7 +232,6 @@
             removeTaskFromEventReceiver(oldTaskInfo.displayId);
             incrementEventReceiverTasks(taskInfo.displayId);
         }
-
         decoration.relayout(taskInfo);
     }
 
@@ -599,7 +597,7 @@
             }
             case MotionEvent.ACTION_UP: {
                 if (relevantDecor == null) {
-                    mDragToDesktopAnimationStarted = false;
+                    mMoveToDesktopAnimator = null;
                     mTransitionDragActive = false;
                     return;
                 }
@@ -613,14 +611,14 @@
                         } else if (DesktopModeStatus.isProto1Enabled()) {
                             mDesktopModeController.ifPresent(c -> c.setDesktopModeActive(true));
                         }
-                        mDragToDesktopAnimationStarted = false;
+                        mMoveToDesktopAnimator = null;
                         return;
-                    } else if (mDragToDesktopAnimationStarted) {
-                        Point position = new Point((int) ev.getX(), (int) ev.getY());
+                    } else if (mMoveToDesktopAnimator != null) {
                         relevantDecor.incrementRelayoutBlock();
                         mDesktopTasksController.ifPresent(
-                                c -> c.cancelMoveToFreeform(relevantDecor.mTaskInfo, position));
-                        mDragToDesktopAnimationStarted = false;
+                                c -> c.cancelMoveToFreeform(relevantDecor.mTaskInfo,
+                                        mMoveToDesktopAnimator));
+                        mMoveToDesktopAnimator = null;
                         return;
                     }
                 }
@@ -640,21 +638,19 @@
                     final int statusBarHeight = getStatusBarHeight(
                             relevantDecor.mTaskInfo.displayId);
                     if (ev.getY() > statusBarHeight) {
-                        if (!mDragToDesktopAnimationStarted) {
-                            mDragToDesktopAnimationStarted = true;
+                        if (mMoveToDesktopAnimator == null) {
+                            mMoveToDesktopAnimator = new MoveToDesktopAnimator(
+                                    mDragToDesktopAnimationStartBounds, relevantDecor.mTaskInfo,
+                                    relevantDecor.mTaskSurface);
                             mDesktopTasksController.ifPresent(
                                     c -> c.moveToFreeform(relevantDecor.mTaskInfo,
-                                            mDragToDesktopAnimationStartBounds));
-                            startAnimation(relevantDecor);
+                                            mDragToDesktopAnimationStartBounds,
+                                            mMoveToDesktopAnimator));
+                            mMoveToDesktopAnimator.startAnimation();
                         }
                     }
-                    if (mDragToDesktopAnimationStarted) {
-                        Transaction t = mTransactionFactory.get();
-                        float width = (float) mDragToDesktopValueAnimator.getAnimatedValue()
-                                * mDragToDesktopAnimationStartBounds.width();
-                        float x = ev.getX() - (width / 2);
-                        t.setPosition(relevantDecor.mTaskSurface, x, ev.getY());
-                        t.apply();
+                    if (mMoveToDesktopAnimator != null) {
+                        mMoveToDesktopAnimator.updatePosition(ev);
                     }
                 }
                 break;
@@ -662,7 +658,7 @@
 
             case MotionEvent.ACTION_CANCEL: {
                 mTransitionDragActive = false;
-                mDragToDesktopAnimationStarted = false;
+                mMoveToDesktopAnimator = null;
             }
         }
     }
@@ -729,20 +725,6 @@
         animator.start();
     }
 
-    private void startAnimation(@NonNull DesktopModeWindowDecoration focusedDecor) {
-        mDragToDesktopValueAnimator = ValueAnimator.ofFloat(1f, DRAG_FREEFORM_SCALE);
-        mDragToDesktopValueAnimator.setDuration(FREEFORM_ANIMATION_DURATION);
-        final Transaction t = mTransactionFactory.get();
-        mDragToDesktopValueAnimator.addUpdateListener(animation -> {
-            final float animatorValue = (float) animation.getAnimatedValue();
-            SurfaceControl sc = focusedDecor.mTaskSurface;
-            t.setScale(sc, animatorValue, animatorValue);
-            t.apply();
-        });
-
-        mDragToDesktopValueAnimator.start();
-    }
-
     @Nullable
     private DesktopModeWindowDecoration getRelevantWindowDecor(MotionEvent ev) {
         if (mSplitScreenController != null && mSplitScreenController.isSplitScreenVisible()) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
index bc89385..a359395 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
@@ -234,7 +234,9 @@
                     mDisplay.getDisplayId(),
                     mRelayoutParams.mCornerRadius,
                     mDecorationContainerSurface,
-                    mDragPositioningCallback);
+                    mDragPositioningCallback,
+                    mSurfaceControlBuilderSupplier,
+                    mSurfaceControlTransactionSupplier);
         }
 
         final int touchSlop = ViewConfiguration.get(mResult.mRootView.getContext())
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragPositioningCallback.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragPositioningCallback.java
index 4e98f0c..941617d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragPositioningCallback.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragPositioningCallback.java
@@ -22,7 +22,9 @@
  * Callback called when receiving drag-resize or drag-move related input events.
  */
 public interface DragPositioningCallback {
-    @IntDef({CTRL_TYPE_UNDEFINED, CTRL_TYPE_LEFT, CTRL_TYPE_RIGHT, CTRL_TYPE_TOP, CTRL_TYPE_BOTTOM})
+    @IntDef(flag = true, value = {
+            CTRL_TYPE_UNDEFINED, CTRL_TYPE_LEFT, CTRL_TYPE_RIGHT, CTRL_TYPE_TOP, CTRL_TYPE_BOTTOM
+    })
     @interface CtrlType {}
 
     int CTRL_TYPE_UNDEFINED = 0;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java
index e5fc66a..7c6fb99 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java
@@ -18,8 +18,11 @@
 
 import static android.view.InputDevice.SOURCE_TOUCHSCREEN;
 import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
+import static android.view.WindowManager.LayoutParams.INPUT_FEATURE_NO_INPUT_CHANNEL;
+import static android.view.WindowManager.LayoutParams.INPUT_FEATURE_SPY;
 import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY;
 import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION;
+import static android.view.WindowManager.LayoutParams.TYPE_INPUT_CONSUMER;
 
 import static com.android.wm.shell.windowdecor.DragPositioningCallback.CTRL_TYPE_BOTTOM;
 import static com.android.wm.shell.windowdecor.DragPositioningCallback.CTRL_TYPE_LEFT;
@@ -48,6 +51,8 @@
 
 import com.android.internal.view.BaseIWindow;
 
+import java.util.function.Supplier;
+
 /**
  * An input event listener registered to InputDispatcher to receive input events on task edges and
  * and corners. Converts them to drag resize requests.
@@ -60,6 +65,7 @@
     private final Handler mHandler;
     private final Choreographer mChoreographer;
     private final InputManager mInputManager;
+    private final Supplier<SurfaceControl.Transaction> mSurfaceControlTransactionSupplier;
 
     private final int mDisplayId;
     private final BaseIWindow mFakeWindow;
@@ -69,6 +75,10 @@
     private final TaskResizeInputEventReceiver mInputEventReceiver;
     private final DragPositioningCallback mCallback;
 
+    private final SurfaceControl mInputSinkSurface;
+    private final BaseIWindow mFakeSinkWindow;
+    private final InputChannel mSinkInputChannel;
+
     private int mTaskWidth;
     private int mTaskHeight;
     private int mResizeHandleThickness;
@@ -90,15 +100,18 @@
             int displayId,
             int taskCornerRadius,
             SurfaceControl decorationSurface,
-            DragPositioningCallback callback) {
+            DragPositioningCallback callback,
+            Supplier<SurfaceControl.Builder> surfaceControlBuilderSupplier,
+            Supplier<SurfaceControl.Transaction> surfaceControlTransactionSupplier) {
         mInputManager = context.getSystemService(InputManager.class);
         mHandler = handler;
         mChoreographer = choreographer;
+        mSurfaceControlTransactionSupplier = surfaceControlTransactionSupplier;
         mDisplayId = displayId;
         mTaskCornerRadius = taskCornerRadius;
         mDecorationSurface = decorationSurface;
-        // Use a fake window as the backing surface is a container layer and we don't want to create
-        // a buffer layer for it so we can't use ViewRootImpl.
+        // Use a fake window as the backing surface is a container layer, and we don't want to
+        // create a buffer layer for it, so we can't use ViewRootImpl.
         mFakeWindow = new BaseIWindow();
         mFakeWindow.setSession(mWindowSession);
         mFocusGrantToken = new Binder();
@@ -111,7 +124,7 @@
                     null /* hostInputToken */,
                     FLAG_NOT_FOCUSABLE,
                     PRIVATE_FLAG_TRUSTED_OVERLAY,
-                    0 /* inputFeatures */,
+                    INPUT_FEATURE_SPY,
                     TYPE_APPLICATION,
                     null /* windowToken */,
                     mFocusGrantToken,
@@ -126,6 +139,35 @@
         mCallback = callback;
         mDragDetector = new DragDetector(mInputEventReceiver);
         mDragDetector.setTouchSlop(ViewConfiguration.get(context).getScaledTouchSlop());
+
+        mInputSinkSurface = surfaceControlBuilderSupplier.get()
+                .setName("TaskInputSink of " + decorationSurface)
+                .setContainerLayer()
+                .setParent(mDecorationSurface)
+                .build();
+        mSurfaceControlTransactionSupplier.get()
+                .setLayer(mInputSinkSurface, WindowDecoration.INPUT_SINK_Z_ORDER)
+                .show(mInputSinkSurface)
+                .apply();
+        mFakeSinkWindow = new BaseIWindow();
+        mSinkInputChannel = new InputChannel();
+        try {
+            mWindowSession.grantInputChannel(
+                    mDisplayId,
+                    mInputSinkSurface,
+                    mFakeSinkWindow,
+                    null /* hostInputToken */,
+                    FLAG_NOT_FOCUSABLE,
+                    0 /* privateFlags */,
+                    INPUT_FEATURE_NO_INPUT_CHANNEL,
+                    TYPE_INPUT_CONSUMER,
+                    null /* windowToken */,
+                    mFocusGrantToken,
+                    "TaskInputSink of " + decorationSurface,
+                    mSinkInputChannel);
+        } catch (RemoteException e) {
+            e.rethrowFromSystemServer();
+        }
     }
 
     /**
@@ -219,7 +261,35 @@
                     mDecorationSurface,
                     FLAG_NOT_FOCUSABLE,
                     PRIVATE_FLAG_TRUSTED_OVERLAY,
-                    0 /* inputFeatures */,
+                    INPUT_FEATURE_SPY,
+                    touchRegion);
+        } catch (RemoteException e) {
+            e.rethrowFromSystemServer();
+        }
+
+        mSurfaceControlTransactionSupplier.get()
+                .setWindowCrop(mInputSinkSurface, mTaskWidth, mTaskHeight)
+                .apply();
+        // The touch region of the TaskInputSink should be the touch region of this
+        // DragResizeInputHandler minus the task bounds. Pilfering events isn't enough to prevent
+        // input windows from handling down events, which will bring tasks in the back to front.
+        //
+        // Note not the entire touch region responds to both mouse and touchscreen events.
+        // Therefore, in the region that only responds to one of them, it would be a no-op to
+        // perform a gesture in the other type of events. We currently only have a mouse-only region
+        // out of the task bounds, and due to the roughness of touchscreen events, it's not a severe
+        // issue. However, were there touchscreen-only a region out of the task bounds, mouse
+        // gestures will become no-op in that region, even though the mouse gestures may appear to
+        // be performed on the input window behind the resize handle.
+        touchRegion.op(0, 0, mTaskWidth, mTaskHeight, Region.Op.DIFFERENCE);
+        try {
+            mWindowSession.updateInputChannel(
+                    mSinkInputChannel.getToken(),
+                    mDisplayId,
+                    mInputSinkSurface,
+                    FLAG_NOT_FOCUSABLE,
+                    0 /* privateFlags */,
+                    INPUT_FEATURE_NO_INPUT_CHANNEL,
                     touchRegion);
         } catch (RemoteException e) {
             e.rethrowFromSystemServer();
@@ -248,6 +318,16 @@
         } catch (RemoteException e) {
             e.rethrowFromSystemServer();
         }
+
+        mSinkInputChannel.dispose();
+        try {
+            mWindowSession.remove(mFakeSinkWindow);
+        } catch (RemoteException e) {
+            e.rethrowFromSystemServer();
+        }
+        mSurfaceControlTransactionSupplier.get()
+                .remove(mInputSinkSurface)
+                .apply();
     }
 
     private class TaskResizeInputEventReceiver extends InputEventReceiver
@@ -256,6 +336,7 @@
         private final Runnable mConsumeBatchEventRunnable;
         private boolean mConsumeBatchEventScheduled;
         private boolean mShouldHandleEvents;
+        private int mLastCursorType = PointerIcon.TYPE_DEFAULT;
 
         private TaskResizeInputEventReceiver(
                 InputChannel inputChannel, Handler handler, Choreographer choreographer) {
@@ -316,6 +397,8 @@
                         mShouldHandleEvents = isInResizeHandleBounds(x, y);
                     }
                     if (mShouldHandleEvents) {
+                        mInputManager.pilferPointers(mInputChannel.getToken());
+
                         mDragPointerId = e.getPointerId(0);
                         float rawX = e.getRawX(0);
                         float rawY = e.getRawY(0);
@@ -355,7 +438,6 @@
                     break;
                 }
                 case MotionEvent.ACTION_HOVER_EXIT:
-                    mInputManager.setPointerIconType(PointerIcon.TYPE_DEFAULT);
                     result = true;
                     break;
             }
@@ -395,7 +477,13 @@
             if (y > mTaskHeight - mTaskCornerRadius) {
                 ctrlType |= CTRL_TYPE_BOTTOM;
             }
-            return checkDistanceFromCenter(ctrlType, x, y);
+            // Check distances from the center if it's in one of four corners.
+            if ((ctrlType & (CTRL_TYPE_LEFT | CTRL_TYPE_RIGHT)) != 0
+                    && (ctrlType & (CTRL_TYPE_TOP | CTRL_TYPE_BOTTOM)) != 0) {
+                return checkDistanceFromCenter(ctrlType, x, y);
+            }
+            // Otherwise, we should make sure we don't resize tasks inside task bounds.
+            return (x < 0 || y < 0 || x >= mTaskWidth || y >= mTaskHeight) ? ctrlType : 0;
         }
 
         // If corner input is not within appropriate distance of corner radius, do not use it.
@@ -429,7 +517,8 @@
                     break;
                 }
                 default: {
-                    return ctrlType;
+                    throw new IllegalArgumentException("ctrlType should be complex, but it's 0x"
+                            + Integer.toHexString(ctrlType));
                 }
             }
             double distanceFromCenter = Math.hypot(x - centerX, y - centerY);
@@ -482,7 +571,19 @@
                     cursorType = PointerIcon.TYPE_TOP_RIGHT_DIAGONAL_DOUBLE_ARROW;
                     break;
             }
-            mInputManager.setPointerIconType(cursorType);
+            // Only update the cursor type to default once so that views behind the decor container
+            // layer that aren't in the active resizing regions have chances to update the cursor
+            // type. We would like to enforce the cursor type by setting the cursor type multilple
+            // times in active regions because we shouldn't allow the views behind to change it, as
+            // we'll pilfer the gesture initiated in this area. This is necessary because 1) we
+            // should allow the views behind regions only for touches to set the cursor type; and 2)
+            // there is a small region out of each rounded corner that's inside the task bounds,
+            // where views in the task can receive input events because we can't set touch regions
+            // of input sinks to have rounded corners.
+            if (mLastCursorType != cursorType || cursorType != PointerIcon.TYPE_DEFAULT) {
+                mInputManager.setPointerIconType(cursorType);
+                mLastCursorType = cursorType;
+            }
         }
     }
 }
\ No newline at end of file
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 917abf5..e1b6db5 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
@@ -63,8 +63,6 @@
         mDragStartListener = dragStartListener;
         mTransactionSupplier = supplier;
         mDisallowedAreaForEndBoundsHeight = disallowedAreaForEndBoundsHeight;
-        mDisplayController.getDisplayLayout(windowDecoration.mDisplay.getDisplayId())
-                .getStableBounds(mStableBounds);
     }
 
     @Override
@@ -80,6 +78,10 @@
             mTaskOrganizer.applyTransaction(wct);
         }
         mRepositionTaskBounds.set(mTaskBoundsAtDragStart);
+        if (mStableBounds.isEmpty()) {
+            mDisplayController.getDisplayLayout(mWindowDecoration.mDisplay.getDisplayId())
+                    .getStableBounds(mStableBounds);
+        }
     }
 
     @Override
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MoveToDesktopAnimator.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MoveToDesktopAnimator.kt
new file mode 100644
index 0000000..b2267dd
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MoveToDesktopAnimator.kt
@@ -0,0 +1,72 @@
+package com.android.wm.shell.windowdecor
+
+import android.animation.ValueAnimator
+import android.app.ActivityManager.RunningTaskInfo
+import android.graphics.PointF
+import android.graphics.Rect
+import android.view.MotionEvent
+import android.view.SurfaceControl
+
+/**
+ * Creates an animator to shrink and position task after a user drags a fullscreen task from
+ * the top of the screen to transition it into freeform and before the user releases the task. The
+ * MoveToDesktopAnimator object also holds information about the state of the task that are
+ * accessed by the EnterDesktopTaskTransitionHandler.
+ */
+class MoveToDesktopAnimator @JvmOverloads constructor(
+        private val startBounds: Rect,
+        private val taskInfo: RunningTaskInfo,
+        private val taskSurface: SurfaceControl,
+        private val transactionFactory: () -> SurfaceControl.Transaction =
+                SurfaceControl::Transaction
+) {
+    companion object {
+        // The size of the screen during drag relative to the fullscreen size
+        const val DRAG_FREEFORM_SCALE: Float = 0.4f
+        const val ANIMATION_DURATION = 336
+    }
+
+    private val animatedTaskWidth
+        get() = dragToDesktopAnimator.animatedValue as Float * startBounds.width()
+    private val dragToDesktopAnimator: ValueAnimator = ValueAnimator.ofFloat(1f,
+            DRAG_FREEFORM_SCALE)
+            .setDuration(ANIMATION_DURATION.toLong())
+            .apply {
+                val t = SurfaceControl.Transaction()
+                addUpdateListener { animation ->
+                    val animatorValue = animation.animatedValue as Float
+                    t.setScale(taskSurface, animatorValue, animatorValue)
+                            .apply()
+                }
+            }
+
+    val taskId get() = taskInfo.taskId
+    val position: PointF = PointF(0.0f, 0.0f)
+
+    /**
+     * Starts the animation that scales the task down.
+     */
+    fun startAnimation() {
+        dragToDesktopAnimator.start()
+    }
+
+    /**
+     * Uses the position of the motion event and the current scale of the task as defined by the
+     * ValueAnimator to update the local position variable and set the task surface's position
+     */
+    fun updatePosition(ev: MotionEvent) {
+        position.x = ev.x - animatedTaskWidth / 2
+        position.y = ev.y
+
+        val t = transactionFactory()
+        t.setPosition(taskSurface, position.x, position.y)
+        t.apply()
+    }
+
+    /**
+     * Ends the animation, setting the scale and position to the final animation value
+     */
+    fun endAnimator() {
+        dragToDesktopAnimator.end()
+    }
+}
\ No newline at end of file
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 bf3ff3f..ae3b5eb 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
@@ -80,8 +80,6 @@
         mTransactionSupplier = supplier;
         mTransitions = transitions;
         mDisallowedAreaForEndBoundsHeight = disallowedAreaForEndBoundsHeight;
-        mDisplayController.getDisplayLayout(windowDecoration.mDisplay.getDisplayId())
-                .getStableBounds(mStableBounds);
     }
 
     @Override
@@ -100,6 +98,10 @@
         }
         mDragStartListener.onDragStart(mDesktopWindowDecoration.mTaskInfo.taskId);
         mRepositionTaskBounds.set(mTaskBoundsAtDragStart);
+        if (mStableBounds.isEmpty()) {
+            mDisplayController.getDisplayLayout(mDesktopWindowDecoration.mDisplay.getDisplayId())
+                    .getStableBounds(mStableBounds);
+        }
     }
 
     @Override
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java
index ddc7fef..0b0d9d5 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java
@@ -64,6 +64,24 @@
         implements AutoCloseable {
 
     /**
+     * The Z-order of {@link #mCaptionContainerSurface}.
+     * <p>
+     * We use {@link #mDecorationContainerSurface} to define input window for task resizing; by
+     * layering it in front of {@link #mCaptionContainerSurface}, we can allow it to handle input
+     * prior to caption view itself, treating corner inputs as resize events rather than
+     * repositioning.
+     */
+    static final int CAPTION_LAYER_Z_ORDER = -1;
+    /**
+     * The Z-order of the task input sink in {@link DragPositioningCallback}.
+     * <p>
+     * This task input sink is used to prevent undesired dispatching of motion events out of task
+     * bounds; by layering it behind {@link #mCaptionContainerSurface}, we allow captions to handle
+     * input events first.
+     */
+    static final int INPUT_SINK_Z_ORDER = -2;
+
+    /**
      * System-wide context. Only used to create context with overridden configurations.
      */
     final Context mContext;
@@ -238,11 +256,8 @@
         final int captionHeight = loadDimensionPixelSize(resources, params.mCaptionHeightId);
         final int captionWidth = taskBounds.width();
 
-        // We use mDecorationContainerSurface to define input window for task resizing; by layering
-        // it in front of mCaptionContainerSurface, we can allow it to handle input prior to
-        // caption view itself, treating corner inputs as resize events rather than repositioning.
         startT.setWindowCrop(mCaptionContainerSurface, captionWidth, captionHeight)
-                .setLayer(mCaptionContainerSurface, -1)
+                .setLayer(mCaptionContainerSurface, CAPTION_LAYER_Z_ORDER)
                 .show(mCaptionContainerSurface);
 
         if (ViewRootImpl.CAPTION_ON_SHELL) {
diff --git a/libs/WindowManager/Shell/tests/flicker/Android.bp b/libs/WindowManager/Shell/tests/flicker/Android.bp
index e382a0f..89747df 100644
--- a/libs/WindowManager/Shell/tests/flicker/Android.bp
+++ b/libs/WindowManager/Shell/tests/flicker/Android.bp
@@ -30,19 +30,26 @@
 
 filegroup {
     name: "WMShellFlickerTestsBubbles-src",
-    srcs: ["src/**/bubble/*.kt"],
+    srcs: ["src/com/android/wm/shell/flicker/bubble/*.kt"],
 }
 
 filegroup {
     name: "WMShellFlickerTestsPip-src",
-    srcs: ["src/**/pip/*.kt"],
+    srcs: ["src/com/android/wm/shell/flicker/pip/*.kt"],
 }
 
 filegroup {
     name: "WMShellFlickerTestsSplitScreen-src",
     srcs: [
-        "src/**/splitscreen/*.kt",
-        "src/**/splitscreen/benchmark/*.kt",
+        "src/com/android/wm/shell/flicker/splitscreen/*.kt",
+        "src/com/android/wm/shell/flicker/splitscreen/benchmark/*.kt",
+    ],
+}
+
+filegroup {
+    name: "WMShellFlickerServiceTests-src",
+    srcs: [
+        "src/com/android/wm/shell/flicker/service/**/*.kt",
     ],
 }
 
@@ -88,6 +95,7 @@
         ":WMShellFlickerTestsBubbles-src",
         ":WMShellFlickerTestsPip-src",
         ":WMShellFlickerTestsSplitScreen-src",
+        ":WMShellFlickerServiceTests-src",
     ],
 }
 
@@ -126,3 +134,15 @@
         ":WMShellFlickerTestsSplitScreen-src",
     ],
 }
+
+android_test {
+    name: "WMShellFlickerServiceTests",
+    defaults: ["WMShellFlickerTestsDefault"],
+    additional_manifests: ["manifests/AndroidManifestSplitScreen.xml"],
+    package_name: "com.android.wm.shell.flicker.service",
+    instrumentation_target_package: "com.android.wm.shell.flicker.service",
+    srcs: [
+        ":WMShellFlickerTestsBase-src",
+        ":WMShellFlickerServiceTests-src",
+    ],
+}
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/SplitScreenUtils.kt
similarity index 96%
rename from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SplitScreenUtils.kt
rename to libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/SplitScreenUtils.kt
index f4828f1..fd56a6e 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/SplitScreenUtils.kt
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2022 The Android Open Source Project
+ * 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.
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.wm.shell.flicker.splitscreen
+package com.android.wm.shell.flicker
 
 import android.app.Instrumentation
 import android.graphics.Point
@@ -40,8 +40,6 @@
 import com.android.server.wm.flicker.helpers.SimpleAppHelper
 import com.android.server.wm.flicker.testapp.ActivityOptions
 import com.android.server.wm.flicker.testapp.ActivityOptions.SplitScreen.Primary
-import com.android.wm.shell.flicker.LAUNCHER_UI_PACKAGE_NAME
-import com.android.wm.shell.flicker.SYSTEM_UI_PACKAGE_NAME
 import org.junit.Assert.assertNotNull
 
 internal object SplitScreenUtils {
@@ -114,13 +112,12 @@
     }
 
     fun enterSplitViaIntent(
-            wmHelper: WindowManagerStateHelper,
-            primaryApp: StandardAppHelper,
-            secondaryApp: StandardAppHelper
+        wmHelper: WindowManagerStateHelper,
+        primaryApp: StandardAppHelper,
+        secondaryApp: StandardAppHelper
     ) {
         val stringExtras = mapOf(Primary.EXTRA_LAUNCH_ADJACENT to "true")
-        primaryApp.launchViaIntent(wmHelper, null, null,
-                stringExtras)
+        primaryApp.launchViaIntent(wmHelper, null, null, stringExtras)
         waitForSplitComplete(wmHelper, primaryApp, secondaryApp)
     }
 
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/QuickSwitchLauncherToLetterboxAppTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/QuickSwitchLauncherToLetterboxAppTest.kt
new file mode 100644
index 0000000..d3f3c5b
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/QuickSwitchLauncherToLetterboxAppTest.kt
@@ -0,0 +1,275 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.appcompat
+
+import android.platform.test.annotations.Postsubmit
+import android.platform.test.annotations.RequiresDevice
+import android.tools.common.NavBar
+import android.tools.common.Rotation
+import android.tools.common.datatypes.Rect
+import android.tools.common.flicker.assertions.FlickerTest
+import android.tools.common.traces.component.ComponentNameMatcher
+import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
+import android.tools.device.flicker.legacy.FlickerBuilder
+import android.tools.device.flicker.legacy.LegacyFlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
+import org.junit.FixMethodOrder
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+
+/**
+ * Test quick switching to letterboxed app from launcher
+ *
+ * To run this test: `atest WMShellFlickerTestsOther:QuickSwitchLauncherToLetterboxAppTest`
+ *
+ * Actions:
+ * ```
+ *     Launch a letterboxed app
+ *     Navigate home to show launcher
+ *     Swipe right from the bottom of the screen to quick switch back to the app
+ * ```
+ */
+@RequiresDevice
+@RunWith(Parameterized::class)
+@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+open class QuickSwitchLauncherToLetterboxAppTest(flicker: LegacyFlickerTest) :
+    BaseAppCompat(flicker) {
+
+    /** {@inheritDoc} */
+    override val transition: FlickerBuilder.() -> Unit = {
+        setup {
+            tapl.setExpectedRotationCheckEnabled(false)
+
+            tapl.setExpectedRotation(flicker.scenario.startRotation.value)
+
+            letterboxApp.launchViaIntent(wmHelper)
+            tapl.goHome()
+            wmHelper
+                .StateSyncBuilder()
+                .withHomeActivityVisible()
+                .withWindowSurfaceDisappeared(letterboxApp)
+                .waitForAndVerify()
+
+            startDisplayBounds =
+                wmHelper.currentState.layerState.physicalDisplayBounds ?: error("Display not found")
+        }
+        transitions {
+            tapl.workspace.quickSwitchToPreviousApp()
+            wmHelper
+                .StateSyncBuilder()
+                .withFullScreenApp(letterboxApp)
+                .withNavOrTaskBarVisible()
+                .withStatusBarVisible()
+                .waitForAndVerify()
+        }
+        teardown { letterboxApp.exit(wmHelper) }
+    }
+
+    /**
+     * Checks that [letterboxApp] is the top window at the end of the transition once we have fully
+     * quick switched from the launcher back to the [letterboxApp].
+     */
+    @Postsubmit
+    @Test
+    fun endsWithAppBeingOnTop() {
+        flicker.assertWmEnd { this.isAppWindowOnTop(letterboxApp) }
+    }
+
+    /** Checks that the transition starts with the home activity being tagged as visible. */
+    @Postsubmit
+    @Test
+    fun startsWithHomeActivityFlaggedVisible() {
+        flicker.assertWmStart { this.isHomeActivityVisible() }
+    }
+
+    /**
+     * Checks that the transition starts with the [ComponentNameMatcher.LAUNCHER] windows
+     * filling/covering exactly display size
+     */
+    @Postsubmit
+    @Test
+    fun startsWithLauncherWindowsCoverFullScreen() {
+        flicker.assertWmStart {
+            this.visibleRegion(ComponentNameMatcher.LAUNCHER).coversExactly(startDisplayBounds)
+        }
+    }
+
+    /**
+     * Checks that the transition starts with the [ComponentNameMatcher.LAUNCHER] layers
+     * filling/covering exactly the display size.
+     */
+    @Postsubmit
+    @Test
+    fun startsWithLauncherLayersCoverFullScreen() {
+        flicker.assertLayersStart {
+            this.visibleRegion(ComponentNameMatcher.LAUNCHER).coversExactly(startDisplayBounds)
+        }
+    }
+
+    /**
+     * Checks that the transition starts with the [ComponentNameMatcher.LAUNCHER] being the top
+     * window.
+     */
+    @Postsubmit
+    @Test
+    fun startsWithLauncherBeingOnTop() {
+        flicker.assertWmStart { this.isAppWindowOnTop(ComponentNameMatcher.LAUNCHER) }
+    }
+
+    /**
+     * Checks that the transition ends with the home activity being flagged as not visible. By this
+     * point we should have quick switched away from the launcher back to the [letterboxApp].
+     */
+    @Postsubmit
+    @Test
+    fun endsWithHomeActivityFlaggedInvisible() {
+        flicker.assertWmEnd { this.isHomeActivityInvisible() }
+    }
+
+    /**
+     * Checks that [letterboxApp]'s window starts off invisible and becomes visible at some point
+     * before the end of the transition and then stays visible until the end of the transition.
+     */
+    @Postsubmit
+    @Test
+    fun appWindowBecomesAndStaysVisible() {
+        flicker.assertWm {
+            this.isAppWindowInvisible(letterboxApp).then().isAppWindowVisible(letterboxApp)
+        }
+    }
+
+    /**
+     * Checks that [letterboxApp]'s layer starts off invisible and becomes visible at some point
+     * before the end of the transition and then stays visible until the end of the transition.
+     */
+    @Postsubmit
+    @Test
+    fun appLayerBecomesAndStaysVisible() {
+        flicker.assertLayers { this.isInvisible(letterboxApp).then().isVisible(letterboxApp) }
+    }
+
+    /**
+     * Checks that the [ComponentNameMatcher.LAUNCHER] window starts off visible and becomes
+     * invisible at some point before the end of the transition and then stays invisible until the
+     * end of the transition.
+     */
+    @Postsubmit
+    @Test
+    fun launcherWindowBecomesAndStaysInvisible() {
+        flicker.assertWm {
+            this.isAppWindowOnTop(ComponentNameMatcher.LAUNCHER)
+                .then()
+                .isAppWindowNotOnTop(ComponentNameMatcher.LAUNCHER)
+        }
+    }
+
+    /**
+     * Checks that the [ComponentNameMatcher.LAUNCHER] layer starts off visible and becomes
+     * invisible at some point before the end of the transition and then stays invisible until the
+     * end of the transition.
+     */
+    @Postsubmit
+    @Test
+    fun launcherLayerBecomesAndStaysInvisible() {
+        flicker.assertLayers {
+            this.isVisible(ComponentNameMatcher.LAUNCHER)
+                .then()
+                .isInvisible(ComponentNameMatcher.LAUNCHER)
+        }
+    }
+
+    /**
+     * Checks that the [ComponentNameMatcher.LAUNCHER] window is visible at least until the app
+     * window is visible. Ensures that at any point, either the launcher or [letterboxApp] windows
+     * are at least partially visible.
+     */
+    @Postsubmit
+    @Test
+    fun appWindowIsVisibleOnceLauncherWindowIsInvisible() {
+        flicker.assertWm {
+            this.isAppWindowOnTop(ComponentNameMatcher.LAUNCHER)
+                .then()
+                .isAppWindowVisible(ComponentNameMatcher.SNAPSHOT, isOptional = true)
+                .then()
+                .isAppWindowVisible(letterboxApp)
+        }
+    }
+
+    /**
+     * Checks that the [ComponentNameMatcher.LAUNCHER] layer is visible at least until the app layer
+     * is visible. Ensures that at any point, either the launcher or [letterboxApp] layers are at
+     * least partially visible.
+     */
+    @Postsubmit
+    @Test
+    fun appLayerIsVisibleOnceLauncherLayerIsInvisible() {
+        flicker.assertLayers {
+            this.isVisible(ComponentNameMatcher.LAUNCHER)
+                .then()
+                .isVisible(ComponentNameMatcher.SNAPSHOT, isOptional = true)
+                .then()
+                .isVisible(letterboxApp)
+        }
+    }
+
+    /**
+     * Checks that the [ComponentNameMatcher.LETTERBOX] layer is visible as soon as the
+     * [letterboxApp] layer is visible at the end of the transition once we have fully quick
+     * switched from the launcher back to the [letterboxApp].
+     */
+    @Postsubmit
+    @Test
+    fun appAndLetterboxLayersBothVisibleOnceLauncherIsInvisible() {
+        flicker.assertLayers {
+            this.isVisible(ComponentNameMatcher.LAUNCHER)
+                .then()
+                .isVisible(ComponentNameMatcher.SNAPSHOT, isOptional = true)
+                .then()
+                .isVisible(letterboxApp)
+                .isVisible(ComponentNameMatcher.LETTERBOX)
+        }
+    }
+
+    /** {@inheritDoc} */
+    @Postsubmit
+    @Test
+    override fun taskBarLayerIsVisibleAtStartAndEnd() = super.taskBarLayerIsVisibleAtStartAndEnd()
+
+    /** {@inheritDoc} */
+    @Postsubmit
+    @Test
+    override fun visibleLayersShownMoreThanOneConsecutiveEntry() {
+        super.visibleLayersShownMoreThanOneConsecutiveEntry()
+    }
+
+    companion object {
+        /** {@inheritDoc} */
+        private var startDisplayBounds = Rect.EMPTY
+
+        @Parameterized.Parameters(name = "{0}")
+        @JvmStatic
+        fun getParams(): Collection<FlickerTest> {
+            return LegacyFlickerTestFactory.nonRotationTests(
+                supportedNavigationModes = listOf(NavBar.MODE_GESTURAL),
+                supportedRotations = listOf(Rotation.ROTATION_90)
+            )
+        }
+    }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/AutoEnterPipFromSplitScreenOnGoToHomeTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/AutoEnterPipFromSplitScreenOnGoToHomeTest.kt
index 36bbafb..8bd44c3 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/AutoEnterPipFromSplitScreenOnGoToHomeTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/AutoEnterPipFromSplitScreenOnGoToHomeTest.kt
@@ -16,12 +16,9 @@
 
 package com.android.wm.shell.flicker.pip
 
-import android.app.Instrumentation
-import android.os.SystemClock
 import android.platform.test.annotations.Presubmit
 import android.tools.common.NavBar
 import android.tools.common.Rotation
-import android.tools.device.apphelpers.StandardAppHelper
 import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
 import android.tools.device.flicker.legacy.FlickerBuilder
 import android.tools.device.flicker.legacy.LegacyFlickerTest
@@ -29,13 +26,9 @@
 import android.tools.device.helpers.WindowUtils
 import android.tools.device.traces.parsers.toFlickerComponent
 import androidx.test.filters.RequiresDevice
-import androidx.test.uiautomator.By
-import androidx.test.uiautomator.BySelector
-import androidx.test.uiautomator.UiObject2
-import androidx.test.uiautomator.Until
 import com.android.server.wm.flicker.helpers.SimpleAppHelper
 import com.android.server.wm.flicker.testapp.ActivityOptions
-import com.android.wm.shell.flicker.LAUNCHER_UI_PACKAGE_NAME
+import com.android.wm.shell.flicker.SplitScreenUtils
 import org.junit.Assume
 import org.junit.FixMethodOrder
 import org.junit.Test
@@ -73,8 +66,7 @@
     AutoEnterPipOnGoToHomeTest(flicker) {
     private val portraitDisplayBounds = WindowUtils.getDisplayBounds(Rotation.ROTATION_0)
     /** Second app used to enter split screen mode */
-    protected val secondAppForSplitScreen = getSplitScreenApp(instrumentation)
-    fun getSplitScreenApp(instrumentation: Instrumentation): StandardAppHelper =
+    private val secondAppForSplitScreen =
         SimpleAppHelper(
             instrumentation,
             ActivityOptions.SplitScreen.Primary.LABEL,
@@ -88,14 +80,7 @@
                 secondAppForSplitScreen.launchViaIntent(wmHelper)
                 pipApp.launchViaIntent(wmHelper)
                 tapl.goHome()
-                enterSplitScreen()
-                // wait until split screen is established
-                wmHelper
-                    .StateSyncBuilder()
-                    .withWindowSurfaceAppeared(pipApp)
-                    .withWindowSurfaceAppeared(secondAppForSplitScreen)
-                    .withSplitDividerVisible()
-                    .waitForAndVerify()
+                SplitScreenUtils.enterSplit(wmHelper, tapl, device, pipApp, secondAppForSplitScreen)
                 pipApp.enableAutoEnterForPipActivity()
             }
             teardown {
@@ -107,46 +92,6 @@
             transitions { tapl.goHome() }
         }
 
-    // TODO(b/285400227) merge the code in a common utility - this is copied from SplitScreenUtils
-    private val TIMEOUT_MS = 3_000L
-    private val overviewSnapshotSelector: BySelector
-        get() = By.res(LAUNCHER_UI_PACKAGE_NAME, "snapshot")
-    private fun enterSplitScreen() {
-        // Note: The initial split position in landscape is different between tablet and phone.
-        // In landscape, tablet will let the first app split to right side, and phone will
-        // split to left side.
-        if (tapl.isTablet) {
-            // TAPL's currentTask on tablet is sometimes not what we expected if the overview
-            // contains more than 3 task views. We need to use uiautomator directly to find the
-            // second task to split.
-            tapl.workspace.switchToOverview().overviewActions.clickSplit()
-            val snapshots =
-                tapl.device.wait(Until.findObjects(overviewSnapshotSelector), TIMEOUT_MS)
-            if (snapshots == null || snapshots.size < 1) {
-                error("Fail to find a overview snapshot to split.")
-            }
-
-            // Find the second task in the upper right corner in split select mode by sorting
-            // 'left' in descending order and 'top' in ascending order.
-            snapshots.sortWith { t1: UiObject2, t2: UiObject2 ->
-                t2.getVisibleBounds().left - t1.getVisibleBounds().left
-            }
-            snapshots.sortWith { t1: UiObject2, t2: UiObject2 ->
-                t1.getVisibleBounds().top - t2.getVisibleBounds().top
-            }
-            snapshots[0].click()
-        } else {
-            tapl.workspace
-                .switchToOverview()
-                .currentTask
-                .tapMenu()
-                .tapSplitMenuItem()
-                .currentTask
-                .open()
-        }
-        SystemClock.sleep(TIMEOUT_MS)
-    }
-
     @Presubmit
     @Test
     override fun pipOverlayLayerAppearThenDisappear() {
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/service/splitscreen/SplitScreenUtils.kt
similarity index 95%
copy from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SplitScreenUtils.kt
copy to libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/SplitScreenUtils.kt
index f4828f1..e640dc4 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/service/splitscreen/SplitScreenUtils.kt
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.wm.shell.flicker.splitscreen
+package com.android.wm.shell.flicker.service.splitscreen
 
 import android.app.Instrumentation
 import android.graphics.Point
@@ -39,12 +39,11 @@
 import com.android.server.wm.flicker.helpers.NotificationAppHelper
 import com.android.server.wm.flicker.helpers.SimpleAppHelper
 import com.android.server.wm.flicker.testapp.ActivityOptions
-import com.android.server.wm.flicker.testapp.ActivityOptions.SplitScreen.Primary
 import com.android.wm.shell.flicker.LAUNCHER_UI_PACKAGE_NAME
 import com.android.wm.shell.flicker.SYSTEM_UI_PACKAGE_NAME
 import org.junit.Assert.assertNotNull
 
-internal object SplitScreenUtils {
+object SplitScreenUtils {
     private const val TIMEOUT_MS = 3_000L
     private const val DRAG_DURATION_MS = 1_000L
     private const val NOTIFICATION_SCROLLER = "notification_stack_scroller"
@@ -113,17 +112,6 @@
         waitForSplitComplete(wmHelper, primaryApp, secondaryApp)
     }
 
-    fun enterSplitViaIntent(
-            wmHelper: WindowManagerStateHelper,
-            primaryApp: StandardAppHelper,
-            secondaryApp: StandardAppHelper
-    ) {
-        val stringExtras = mapOf(Primary.EXTRA_LAUNCH_ADJACENT to "true")
-        primaryApp.launchViaIntent(wmHelper, null, null,
-                stringExtras)
-        waitForSplitComplete(wmHelper, primaryApp, secondaryApp)
-    }
-
     fun splitFromOverview(tapl: LauncherInstrumentation, device: UiDevice) {
         // Note: The initial split position in landscape is different between tablet and phone.
         // In landscape, tablet will let the first app split to right side, and phone will
@@ -159,6 +147,17 @@
         SystemClock.sleep(TIMEOUT_MS)
     }
 
+    fun enterSplitViaIntent(
+        wmHelper: WindowManagerStateHelper,
+        primaryApp: StandardAppHelper,
+        secondaryApp: StandardAppHelper
+    ) {
+        val stringExtras =
+            mapOf(ActivityOptions.SplitScreen.Primary.EXTRA_LAUNCH_ADJACENT to "true")
+        primaryApp.launchViaIntent(wmHelper, null, null, stringExtras)
+        SplitScreenUtils.waitForSplitComplete(wmHelper, primaryApp, secondaryApp)
+    }
+
     fun dragFromNotificationToSplit(
         instrumentation: Instrumentation,
         device: UiDevice,
@@ -326,14 +325,14 @@
         dividerBar.drag(
             Point(
                 if (dragToRight) {
-                    displayBounds.right
+                    displayBounds.width * 4 / 5
                 } else {
-                    displayBounds.left
+                    displayBounds.width * 1 / 5
                 },
                 if (dragToBottom) {
-                    displayBounds.bottom
+                    displayBounds.height * 4 / 5
                 } else {
-                    displayBounds.top
+                    displayBounds.height * 1 / 5
                 }
             )
         )
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/CopyContentInSplitGesturalNavLandscapeBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/CopyContentInSplitGesturalNavLandscapeBenchmark.kt
new file mode 100644
index 0000000..566adec
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/CopyContentInSplitGesturalNavLandscapeBenchmark.kt
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.service.splitscreen.benchmark
+
+import android.platform.test.annotations.PlatinumTest
+import android.platform.test.annotations.Presubmit
+import android.tools.common.Rotation
+import androidx.test.filters.RequiresDevice
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.CopyContentInSplit
+import org.junit.Test
+
+@RequiresDevice
+class CopyContentInSplitGesturalNavLandscapeBenchmark : CopyContentInSplit(Rotation.ROTATION_90) {
+    @PlatinumTest(focusArea = "sysui")
+    @Presubmit
+    @Test
+    override fun copyContentInSplit() = super.copyContentInSplit()
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/CopyContentInSplitGesturalNavPortraitBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/CopyContentInSplitGesturalNavPortraitBenchmark.kt
new file mode 100644
index 0000000..92b6227
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/CopyContentInSplitGesturalNavPortraitBenchmark.kt
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.service.splitscreen.benchmark
+
+import android.platform.test.annotations.PlatinumTest
+import android.platform.test.annotations.Presubmit
+import android.tools.common.Rotation
+import androidx.test.filters.RequiresDevice
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.CopyContentInSplit
+import org.junit.Test
+
+@RequiresDevice
+class CopyContentInSplitGesturalNavPortraitBenchmark : CopyContentInSplit(Rotation.ROTATION_0) {
+    @PlatinumTest(focusArea = "sysui")
+    @Presubmit
+    @Test
+    override fun copyContentInSplit() = super.copyContentInSplit()
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/DismissSplitScreenByDividerGesturalNavLandscapeBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/DismissSplitScreenByDividerGesturalNavLandscapeBenchmark.kt
new file mode 100644
index 0000000..e6d56b5
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/DismissSplitScreenByDividerGesturalNavLandscapeBenchmark.kt
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.service.splitscreen.benchmark
+
+import android.platform.test.annotations.PlatinumTest
+import android.platform.test.annotations.Presubmit
+import android.tools.common.Rotation
+import androidx.test.filters.RequiresDevice
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.DismissSplitScreenByDivider
+import org.junit.Test
+
+@RequiresDevice
+class DismissSplitScreenByDividerGesturalNavLandscapeBenchmark :
+    DismissSplitScreenByDivider(Rotation.ROTATION_90) {
+    @PlatinumTest(focusArea = "sysui")
+    @Presubmit
+    @Test
+    override fun dismissSplitScreenByDivider() = super.dismissSplitScreenByDivider()
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/DismissSplitScreenByDividerGesturalNavPortraitBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/DismissSplitScreenByDividerGesturalNavPortraitBenchmark.kt
new file mode 100644
index 0000000..6752c58
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/DismissSplitScreenByDividerGesturalNavPortraitBenchmark.kt
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.service.splitscreen.benchmark
+
+import android.platform.test.annotations.PlatinumTest
+import android.platform.test.annotations.Presubmit
+import android.tools.common.Rotation
+import androidx.test.filters.RequiresDevice
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.DismissSplitScreenByDivider
+import org.junit.Test
+
+@RequiresDevice
+class DismissSplitScreenByDividerGesturalNavPortraitBenchmark :
+    DismissSplitScreenByDivider(Rotation.ROTATION_0) {
+    @PlatinumTest(focusArea = "sysui")
+    @Presubmit
+    @Test
+    override fun dismissSplitScreenByDivider() = super.dismissSplitScreenByDivider()
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/DismissSplitScreenByGoHomeGesturalNavLandscapeBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/DismissSplitScreenByGoHomeGesturalNavLandscapeBenchmark.kt
new file mode 100644
index 0000000..7c9ab99
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/DismissSplitScreenByGoHomeGesturalNavLandscapeBenchmark.kt
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.service.splitscreen.benchmark
+
+import android.platform.test.annotations.PlatinumTest
+import android.platform.test.annotations.Presubmit
+import android.tools.common.Rotation
+import androidx.test.filters.RequiresDevice
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.DismissSplitScreenByGoHome
+import org.junit.Test
+
+@RequiresDevice
+class DismissSplitScreenByGoHomeGesturalNavLandscapeBenchmark :
+    DismissSplitScreenByGoHome(Rotation.ROTATION_90) {
+    @PlatinumTest(focusArea = "sysui")
+    @Presubmit
+    @Test
+    override fun dismissSplitScreenByGoHome() = super.dismissSplitScreenByGoHome()
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/DismissSplitScreenByGoHomeGesturalNavPortraitBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/DismissSplitScreenByGoHomeGesturalNavPortraitBenchmark.kt
new file mode 100644
index 0000000..4b79571
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/DismissSplitScreenByGoHomeGesturalNavPortraitBenchmark.kt
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.service.splitscreen.benchmark
+
+import android.platform.test.annotations.PlatinumTest
+import android.platform.test.annotations.Presubmit
+import android.tools.common.Rotation
+import androidx.test.filters.RequiresDevice
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.DismissSplitScreenByGoHome
+import org.junit.Test
+
+@RequiresDevice
+class DismissSplitScreenByGoHomeGesturalNavPortraitBenchmark :
+    DismissSplitScreenByGoHome(Rotation.ROTATION_0) {
+    @PlatinumTest(focusArea = "sysui")
+    @Presubmit
+    @Test
+    override fun dismissSplitScreenByGoHome() = super.dismissSplitScreenByGoHome()
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/DragDividerToResizeGesturalNavLandscapeBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/DragDividerToResizeGesturalNavLandscapeBenchmark.kt
new file mode 100644
index 0000000..0495079
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/DragDividerToResizeGesturalNavLandscapeBenchmark.kt
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.service.splitscreen.benchmark
+
+import android.platform.test.annotations.PlatinumTest
+import android.platform.test.annotations.Presubmit
+import android.tools.common.Rotation
+import androidx.test.filters.RequiresDevice
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.DragDividerToResize
+import org.junit.Test
+
+@RequiresDevice
+class DragDividerToResizeGesturalNavLandscapeBenchmark : DragDividerToResize(Rotation.ROTATION_90) {
+    @PlatinumTest(focusArea = "sysui")
+    @Presubmit
+    @Test
+    override fun dragDividerToResize() = super.dragDividerToResize()
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/DragDividerToResizeGesturalNavPortraitBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/DragDividerToResizeGesturalNavPortraitBenchmark.kt
new file mode 100644
index 0000000..71ef48b
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/DragDividerToResizeGesturalNavPortraitBenchmark.kt
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.service.splitscreen.benchmark
+
+import android.platform.test.annotations.PlatinumTest
+import android.platform.test.annotations.Presubmit
+import android.tools.common.Rotation
+import androidx.test.filters.RequiresDevice
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.DragDividerToResize
+import org.junit.Test
+
+@RequiresDevice
+class DragDividerToResizeGesturalNavPortraitBenchmark : DragDividerToResize(Rotation.ROTATION_0) {
+    @PlatinumTest(focusArea = "sysui")
+    @Presubmit
+    @Test
+    override fun dragDividerToResize() = super.dragDividerToResize()
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenByDragFromAllAppsGesturalNavLandscapeBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenByDragFromAllAppsGesturalNavLandscapeBenchmark.kt
new file mode 100644
index 0000000..c78729c
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenByDragFromAllAppsGesturalNavLandscapeBenchmark.kt
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.service.splitscreen.benchmark
+
+import android.platform.test.annotations.PlatinumTest
+import android.platform.test.annotations.Presubmit
+import android.tools.common.Rotation
+import androidx.test.filters.RequiresDevice
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenByDragFromAllApps
+import org.junit.Test
+
+@RequiresDevice
+class EnterSplitScreenByDragFromAllAppsGesturalNavLandscapeBenchmark :
+    EnterSplitScreenByDragFromAllApps(Rotation.ROTATION_90) {
+    @PlatinumTest(focusArea = "sysui")
+    @Presubmit
+    @Test
+    override fun enterSplitScreenByDragFromAllApps() = super.enterSplitScreenByDragFromAllApps()
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenByDragFromAllAppsGesturalNavPortraitBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenByDragFromAllAppsGesturalNavPortraitBenchmark.kt
new file mode 100644
index 0000000..30bce2f6
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenByDragFromAllAppsGesturalNavPortraitBenchmark.kt
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.service.splitscreen.benchmark
+
+import android.platform.test.annotations.PlatinumTest
+import android.platform.test.annotations.Presubmit
+import android.tools.common.Rotation
+import androidx.test.filters.RequiresDevice
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenByDragFromAllApps
+import org.junit.Test
+
+@RequiresDevice
+class EnterSplitScreenByDragFromAllAppsGesturalNavPortraitBenchmark :
+    EnterSplitScreenByDragFromAllApps(Rotation.ROTATION_0) {
+    @PlatinumTest(focusArea = "sysui")
+    @Presubmit
+    @Test
+    override fun enterSplitScreenByDragFromAllApps() = super.enterSplitScreenByDragFromAllApps()
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenByDragFromNotificationGesturalNavLandscapeBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenByDragFromNotificationGesturalNavLandscapeBenchmark.kt
new file mode 100644
index 0000000..b33ea7c
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenByDragFromNotificationGesturalNavLandscapeBenchmark.kt
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.service.splitscreen.benchmark
+
+import android.platform.test.annotations.PlatinumTest
+import android.platform.test.annotations.Presubmit
+import android.tools.common.Rotation
+import androidx.test.filters.RequiresDevice
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenByDragFromNotification
+import org.junit.Test
+
+@RequiresDevice
+class EnterSplitScreenByDragFromNotificationGesturalNavLandscapeBenchmark :
+    EnterSplitScreenByDragFromNotification(Rotation.ROTATION_90) {
+    @PlatinumTest(focusArea = "sysui")
+    @Presubmit
+    @Test
+    override fun enterSplitScreenByDragFromNotification() =
+        super.enterSplitScreenByDragFromNotification()
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenByDragFromNotificationGesturalNavPortraitBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenByDragFromNotificationGesturalNavPortraitBenchmark.kt
new file mode 100644
index 0000000..07a86a5
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenByDragFromNotificationGesturalNavPortraitBenchmark.kt
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.service.splitscreen.benchmark
+
+import android.platform.test.annotations.PlatinumTest
+import android.platform.test.annotations.Presubmit
+import android.tools.common.Rotation
+import androidx.test.filters.RequiresDevice
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenByDragFromNotification
+import org.junit.Test
+
+@RequiresDevice
+class EnterSplitScreenByDragFromNotificationGesturalNavPortraitBenchmark :
+    EnterSplitScreenByDragFromNotification(Rotation.ROTATION_0) {
+    @PlatinumTest(focusArea = "sysui")
+    @Presubmit
+    @Test
+    override fun enterSplitScreenByDragFromNotification() =
+        super.enterSplitScreenByDragFromNotification()
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenByDragFromShortcutGesturalNavLandscapeBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenByDragFromShortcutGesturalNavLandscapeBenchmark.kt
new file mode 100644
index 0000000..9a1d127
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenByDragFromShortcutGesturalNavLandscapeBenchmark.kt
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.service.splitscreen.benchmark
+
+import android.platform.test.annotations.PlatinumTest
+import android.platform.test.annotations.Presubmit
+import android.tools.common.Rotation
+import androidx.test.filters.RequiresDevice
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenByDragFromShortcut
+import org.junit.Test
+
+@RequiresDevice
+class EnterSplitScreenByDragFromShortcutGesturalNavLandscapeBenchmark :
+    EnterSplitScreenByDragFromShortcut(Rotation.ROTATION_90) {
+    @PlatinumTest(focusArea = "sysui")
+    @Presubmit
+    @Test
+    override fun enterSplitScreenByDragFromShortcut() = super.enterSplitScreenByDragFromShortcut()
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenByDragFromShortcutGesturalNavPortraitBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenByDragFromShortcutGesturalNavPortraitBenchmark.kt
new file mode 100644
index 0000000..266e268
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenByDragFromShortcutGesturalNavPortraitBenchmark.kt
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.service.splitscreen.benchmark
+
+import android.platform.test.annotations.PlatinumTest
+import android.platform.test.annotations.Presubmit
+import android.tools.common.Rotation
+import androidx.test.filters.RequiresDevice
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenByDragFromShortcut
+import org.junit.Test
+
+@RequiresDevice
+class EnterSplitScreenByDragFromShortcutGesturalNavPortraitBenchmark :
+    EnterSplitScreenByDragFromShortcut(Rotation.ROTATION_0) {
+    @PlatinumTest(focusArea = "sysui")
+    @Presubmit
+    @Test
+    override fun enterSplitScreenByDragFromShortcut() = super.enterSplitScreenByDragFromShortcut()
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenByDragFromTaskbarGesturalNavLandscapeBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenByDragFromTaskbarGesturalNavLandscapeBenchmark.kt
new file mode 100644
index 0000000..83fc30b
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenByDragFromTaskbarGesturalNavLandscapeBenchmark.kt
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.service.splitscreen.benchmark
+
+import android.platform.test.annotations.PlatinumTest
+import android.platform.test.annotations.Presubmit
+import android.tools.common.Rotation
+import androidx.test.filters.RequiresDevice
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenByDragFromTaskbar
+import org.junit.Test
+
+@RequiresDevice
+class EnterSplitScreenByDragFromTaskbarGesturalNavLandscapeBenchmark :
+    EnterSplitScreenByDragFromTaskbar(Rotation.ROTATION_90) {
+    @PlatinumTest(focusArea = "sysui")
+    @Presubmit
+    @Test
+    override fun enterSplitScreenByDragFromTaskbar() = super.enterSplitScreenByDragFromTaskbar()
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenByDragFromTaskbarGesturalNavPortraitBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenByDragFromTaskbarGesturalNavPortraitBenchmark.kt
new file mode 100644
index 0000000..b2f1929
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenByDragFromTaskbarGesturalNavPortraitBenchmark.kt
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.service.splitscreen.benchmark
+
+import android.platform.test.annotations.PlatinumTest
+import android.platform.test.annotations.Presubmit
+import android.tools.common.Rotation
+import androidx.test.filters.RequiresDevice
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenByDragFromTaskbar
+import org.junit.Test
+
+@RequiresDevice
+class EnterSplitScreenByDragFromTaskbarGesturalNavPortraitBenchmark :
+    EnterSplitScreenByDragFromTaskbar(Rotation.ROTATION_0) {
+    @PlatinumTest(focusArea = "sysui")
+    @Presubmit
+    @Test
+    override fun enterSplitScreenByDragFromTaskbar() = super.enterSplitScreenByDragFromTaskbar()
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenFromOverviewGesturalNavLandscapeBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenFromOverviewGesturalNavLandscapeBenchmark.kt
new file mode 100644
index 0000000..dae92dd
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenFromOverviewGesturalNavLandscapeBenchmark.kt
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.service.splitscreen.benchmark
+
+import android.platform.test.annotations.PlatinumTest
+import android.platform.test.annotations.Presubmit
+import android.tools.common.Rotation
+import androidx.test.filters.RequiresDevice
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenFromOverview
+import org.junit.Test
+
+@RequiresDevice
+class EnterSplitScreenFromOverviewGesturalNavLandscapeBenchmark :
+    EnterSplitScreenFromOverview(Rotation.ROTATION_90) {
+    @PlatinumTest(focusArea = "sysui")
+    @Presubmit
+    @Test
+    override fun enterSplitScreenFromOverview() = super.enterSplitScreenFromOverview()
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenFromOverviewGesturalNavPortraitBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenFromOverviewGesturalNavPortraitBenchmark.kt
new file mode 100644
index 0000000..732047b
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenFromOverviewGesturalNavPortraitBenchmark.kt
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.service.splitscreen.benchmark
+
+import android.platform.test.annotations.PlatinumTest
+import android.platform.test.annotations.Presubmit
+import android.tools.common.Rotation
+import androidx.test.filters.RequiresDevice
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenFromOverview
+import org.junit.Test
+
+@RequiresDevice
+class EnterSplitScreenFromOverviewGesturalNavPortraitBenchmark :
+    EnterSplitScreenFromOverview(Rotation.ROTATION_0) {
+    @PlatinumTest(focusArea = "sysui")
+    @Presubmit
+    @Test
+    override fun enterSplitScreenFromOverview() = super.enterSplitScreenFromOverview()
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchAppByDoubleTapDividerGesturalNavLandscapeBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchAppByDoubleTapDividerGesturalNavLandscapeBenchmark.kt
new file mode 100644
index 0000000..1de7efd
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchAppByDoubleTapDividerGesturalNavLandscapeBenchmark.kt
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.service.splitscreen.benchmark
+
+import android.platform.test.annotations.PlatinumTest
+import android.platform.test.annotations.Presubmit
+import android.tools.common.Rotation
+import androidx.test.filters.RequiresDevice
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchAppByDoubleTapDivider
+import org.junit.Test
+
+@RequiresDevice
+class SwitchAppByDoubleTapDividerGesturalNavLandscapeBenchmark :
+    SwitchAppByDoubleTapDivider(Rotation.ROTATION_90) {
+    @PlatinumTest(focusArea = "sysui")
+    @Presubmit
+    @Test
+    override fun switchAppByDoubleTapDivider() = super.switchAppByDoubleTapDivider()
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchAppByDoubleTapDividerGesturalNavPortraitBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchAppByDoubleTapDividerGesturalNavPortraitBenchmark.kt
new file mode 100644
index 0000000..1a046aa
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchAppByDoubleTapDividerGesturalNavPortraitBenchmark.kt
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.service.splitscreen.benchmark
+
+import android.platform.test.annotations.PlatinumTest
+import android.platform.test.annotations.Presubmit
+import android.tools.common.Rotation
+import androidx.test.filters.RequiresDevice
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchAppByDoubleTapDivider
+import org.junit.Test
+
+@RequiresDevice
+class SwitchAppByDoubleTapDividerGesturalNavPortraitBenchmark :
+    SwitchAppByDoubleTapDivider(Rotation.ROTATION_0) {
+    @PlatinumTest(focusArea = "sysui")
+    @Presubmit
+    @Test
+    override fun switchAppByDoubleTapDivider() = super.switchAppByDoubleTapDivider()
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchBackToSplitFromAnotherAppGesturalNavLandscapeBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchBackToSplitFromAnotherAppGesturalNavLandscapeBenchmark.kt
new file mode 100644
index 0000000..6e88f0e
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchBackToSplitFromAnotherAppGesturalNavLandscapeBenchmark.kt
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.service.splitscreen.benchmark
+
+import android.platform.test.annotations.PlatinumTest
+import android.platform.test.annotations.Presubmit
+import android.tools.common.Rotation
+import androidx.test.filters.RequiresDevice
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchBackToSplitFromAnotherApp
+import org.junit.Test
+
+@RequiresDevice
+class SwitchBackToSplitFromAnotherAppGesturalNavLandscapeBenchmark :
+    SwitchBackToSplitFromAnotherApp(Rotation.ROTATION_90) {
+    @PlatinumTest(focusArea = "sysui")
+    @Presubmit
+    @Test
+    override fun switchBackToSplitFromAnotherApp() = super.switchBackToSplitFromAnotherApp()
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchBackToSplitFromAnotherAppGesturalNavPortraitBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchBackToSplitFromAnotherAppGesturalNavPortraitBenchmark.kt
new file mode 100644
index 0000000..d26a29c
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchBackToSplitFromAnotherAppGesturalNavPortraitBenchmark.kt
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.service.splitscreen.benchmark
+
+import android.platform.test.annotations.PlatinumTest
+import android.platform.test.annotations.Presubmit
+import android.tools.common.Rotation
+import androidx.test.filters.RequiresDevice
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchBackToSplitFromAnotherApp
+import org.junit.Test
+
+@RequiresDevice
+class SwitchBackToSplitFromAnotherAppGesturalNavPortraitBenchmark :
+    SwitchBackToSplitFromAnotherApp(Rotation.ROTATION_0) {
+    @PlatinumTest(focusArea = "sysui")
+    @Presubmit
+    @Test
+    override fun switchBackToSplitFromAnotherApp() = super.switchBackToSplitFromAnotherApp()
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchBackToSplitFromHomeGesturalNavLandscapeBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchBackToSplitFromHomeGesturalNavLandscapeBenchmark.kt
new file mode 100644
index 0000000..4a552b0
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchBackToSplitFromHomeGesturalNavLandscapeBenchmark.kt
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.service.splitscreen.benchmark
+
+import android.platform.test.annotations.PlatinumTest
+import android.platform.test.annotations.Presubmit
+import android.tools.common.Rotation
+import androidx.test.filters.RequiresDevice
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchBackToSplitFromHome
+import org.junit.Test
+
+@RequiresDevice
+class SwitchBackToSplitFromHomeGesturalNavLandscapeBenchmark :
+    SwitchBackToSplitFromHome(Rotation.ROTATION_90) {
+    @PlatinumTest(focusArea = "sysui")
+    @Presubmit
+    @Test
+    override fun switchBackToSplitFromHome() = super.switchBackToSplitFromHome()
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchBackToSplitFromHomeGesturalNavPortraitBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchBackToSplitFromHomeGesturalNavPortraitBenchmark.kt
new file mode 100644
index 0000000..b7376ea
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchBackToSplitFromHomeGesturalNavPortraitBenchmark.kt
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.service.splitscreen.benchmark
+
+import android.platform.test.annotations.PlatinumTest
+import android.platform.test.annotations.Presubmit
+import android.tools.common.Rotation
+import androidx.test.filters.RequiresDevice
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchBackToSplitFromHome
+import org.junit.Test
+
+@RequiresDevice
+class SwitchBackToSplitFromHomeGesturalNavPortraitBenchmark :
+    SwitchBackToSplitFromHome(Rotation.ROTATION_0) {
+    @PlatinumTest(focusArea = "sysui")
+    @Presubmit
+    @Test
+    override fun switchBackToSplitFromHome() = super.switchBackToSplitFromHome()
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchBackToSplitFromRecentGesturalNavLandscapeBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchBackToSplitFromRecentGesturalNavLandscapeBenchmark.kt
new file mode 100644
index 0000000..b2d05e4
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchBackToSplitFromRecentGesturalNavLandscapeBenchmark.kt
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.service.splitscreen.benchmark
+
+import android.platform.test.annotations.PlatinumTest
+import android.platform.test.annotations.Presubmit
+import android.tools.common.Rotation
+import androidx.test.filters.RequiresDevice
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchBackToSplitFromRecent
+import org.junit.Test
+
+@RequiresDevice
+class SwitchBackToSplitFromRecentGesturalNavLandscapeBenchmark :
+    SwitchBackToSplitFromRecent(Rotation.ROTATION_90) {
+    @PlatinumTest(focusArea = "sysui")
+    @Presubmit
+    @Test
+    override fun switchBackToSplitFromRecent() = super.switchBackToSplitFromRecent()
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchBackToSplitFromRecentGesturalNavPortraitBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchBackToSplitFromRecentGesturalNavPortraitBenchmark.kt
new file mode 100644
index 0000000..6de31b1
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchBackToSplitFromRecentGesturalNavPortraitBenchmark.kt
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.service.splitscreen.benchmark
+
+import android.platform.test.annotations.PlatinumTest
+import android.platform.test.annotations.Presubmit
+import android.tools.common.Rotation
+import androidx.test.filters.RequiresDevice
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchBackToSplitFromRecent
+import org.junit.Test
+
+@RequiresDevice
+class SwitchBackToSplitFromRecentGesturalNavPortraitBenchmark :
+    SwitchBackToSplitFromRecent(Rotation.ROTATION_0) {
+    @PlatinumTest(focusArea = "sysui")
+    @Presubmit
+    @Test
+    override fun switchBackToSplitFromRecent() = super.switchBackToSplitFromRecent()
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchBetweenSplitPairsGesturalNavLandscapeBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchBetweenSplitPairsGesturalNavLandscapeBenchmark.kt
new file mode 100644
index 0000000..aab18a6
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchBetweenSplitPairsGesturalNavLandscapeBenchmark.kt
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.service.splitscreen.benchmark
+
+import android.platform.test.annotations.PlatinumTest
+import android.platform.test.annotations.Presubmit
+import android.tools.common.Rotation
+import androidx.test.filters.RequiresDevice
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchBetweenSplitPairs
+import org.junit.Test
+
+@RequiresDevice
+class SwitchBetweenSplitPairsGesturalNavLandscapeBenchmark :
+    SwitchBetweenSplitPairs(Rotation.ROTATION_90) {
+    @PlatinumTest(focusArea = "sysui")
+    @Presubmit
+    @Test
+    override fun switchBetweenSplitPairs() = super.switchBetweenSplitPairs()
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchBetweenSplitPairsGesturalNavPortraitBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchBetweenSplitPairsGesturalNavPortraitBenchmark.kt
new file mode 100644
index 0000000..b074f2c
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchBetweenSplitPairsGesturalNavPortraitBenchmark.kt
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.service.splitscreen.benchmark
+
+import android.platform.test.annotations.PlatinumTest
+import android.platform.test.annotations.Presubmit
+import android.tools.common.Rotation
+import androidx.test.filters.RequiresDevice
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchBetweenSplitPairs
+import org.junit.Test
+
+@RequiresDevice
+class SwitchBetweenSplitPairsGesturalNavPortraitBenchmark :
+    SwitchBetweenSplitPairs(Rotation.ROTATION_0) {
+    @PlatinumTest(focusArea = "sysui")
+    @Presubmit
+    @Test
+    override fun switchBetweenSplitPairs() = super.switchBetweenSplitPairs()
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/UnlockKeyguardToSplitScreenGesturalNavLandscapeBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/UnlockKeyguardToSplitScreenGesturalNavLandscapeBenchmark.kt
new file mode 100644
index 0000000..c402aa4
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/UnlockKeyguardToSplitScreenGesturalNavLandscapeBenchmark.kt
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.service.splitscreen.benchmark
+
+import android.platform.test.annotations.PlatinumTest
+import android.platform.test.annotations.Presubmit
+import androidx.test.filters.RequiresDevice
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.UnlockKeyguardToSplitScreen
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.BlockJUnit4ClassRunner
+
+@RequiresDevice
+@RunWith(BlockJUnit4ClassRunner::class)
+class UnlockKeyguardToSplitScreenGesturalNavLandscapeBenchmark : UnlockKeyguardToSplitScreen() {
+    @PlatinumTest(focusArea = "sysui")
+    @Presubmit
+    @Test
+    override fun unlockKeyguardToSplitScreen() = super.unlockKeyguardToSplitScreen()
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/UnlockKeyguardToSplitScreenGesturalNavPortraitBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/UnlockKeyguardToSplitScreenGesturalNavPortraitBenchmark.kt
new file mode 100644
index 0000000..840401c
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/UnlockKeyguardToSplitScreenGesturalNavPortraitBenchmark.kt
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.service.splitscreen.benchmark
+
+import android.platform.test.annotations.PlatinumTest
+import android.platform.test.annotations.Presubmit
+import androidx.test.filters.RequiresDevice
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.UnlockKeyguardToSplitScreen
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.BlockJUnit4ClassRunner
+
+@RequiresDevice
+@RunWith(BlockJUnit4ClassRunner::class)
+class UnlockKeyguardToSplitScreenGesturalNavPortraitBenchmark : UnlockKeyguardToSplitScreen() {
+    @PlatinumTest(focusArea = "sysui")
+    @Presubmit
+    @Test
+    override fun unlockKeyguardToSplitScreen() = super.unlockKeyguardToSplitScreen()
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/CopyContentInSplitGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/CopyContentInSplitGesturalNavLandscape.kt
new file mode 100644
index 0000000..a5c5122
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/CopyContentInSplitGesturalNavLandscape.kt
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.service.splitscreen.flicker
+
+import android.tools.common.Rotation
+import android.tools.common.flicker.FlickerConfig
+import android.tools.common.flicker.annotation.ExpectedScenarios
+import android.tools.common.flicker.annotation.FlickerConfigProvider
+import android.tools.common.flicker.config.FlickerConfig
+import android.tools.common.flicker.config.FlickerServiceConfig
+import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.CopyContentInSplit
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class CopyContentInSplitGesturalNavLandscape : CopyContentInSplit(Rotation.ROTATION_90) {
+    @ExpectedScenarios([]) @Test override fun copyContentInSplit() = super.copyContentInSplit()
+
+    companion object {
+        @JvmStatic
+        @FlickerConfigProvider
+        fun flickerConfigProvider(): FlickerConfig =
+            FlickerConfig().use(FlickerServiceConfig.DEFAULT)
+    }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/CopyContentInSplitGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/CopyContentInSplitGesturalNavPortrait.kt
new file mode 100644
index 0000000..092fb67
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/CopyContentInSplitGesturalNavPortrait.kt
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.service.splitscreen.flicker
+
+import android.tools.common.Rotation
+import android.tools.common.flicker.FlickerConfig
+import android.tools.common.flicker.annotation.ExpectedScenarios
+import android.tools.common.flicker.annotation.FlickerConfigProvider
+import android.tools.common.flicker.config.FlickerConfig
+import android.tools.common.flicker.config.FlickerServiceConfig
+import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.CopyContentInSplit
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class CopyContentInSplitGesturalNavPortrait : CopyContentInSplit(Rotation.ROTATION_0) {
+    @ExpectedScenarios([]) @Test override fun copyContentInSplit() = super.copyContentInSplit()
+
+    companion object {
+        @JvmStatic
+        @FlickerConfigProvider
+        fun flickerConfigProvider(): FlickerConfig =
+            FlickerConfig().use(FlickerServiceConfig.DEFAULT)
+    }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/DismissSplitScreenByDividerGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/DismissSplitScreenByDividerGesturalNavLandscape.kt
new file mode 100644
index 0000000..8cb25fe
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/DismissSplitScreenByDividerGesturalNavLandscape.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.wm.shell.flicker.service.splitscreen.flicker
+
+import android.tools.common.Rotation
+import android.tools.common.flicker.FlickerConfig
+import android.tools.common.flicker.annotation.ExpectedScenarios
+import android.tools.common.flicker.annotation.FlickerConfigProvider
+import android.tools.common.flicker.config.FlickerConfig
+import android.tools.common.flicker.config.FlickerServiceConfig
+import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.DismissSplitScreenByDivider
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class DismissSplitScreenByDividerGesturalNavLandscape :
+    DismissSplitScreenByDivider(Rotation.ROTATION_90) {
+
+    @ExpectedScenarios(["SPLIT_SCREEN_EXIT"])
+    @Test
+    override fun dismissSplitScreenByDivider() = super.dismissSplitScreenByDivider()
+
+    companion object {
+        @JvmStatic
+        @FlickerConfigProvider
+        fun flickerConfigProvider(): FlickerConfig =
+            FlickerConfig().use(FlickerServiceConfig.DEFAULT)
+    }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/DismissSplitScreenByDividerGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/DismissSplitScreenByDividerGesturalNavPortrait.kt
new file mode 100644
index 0000000..fa1be63
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/DismissSplitScreenByDividerGesturalNavPortrait.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.wm.shell.flicker.service.splitscreen.flicker
+
+import android.tools.common.Rotation
+import android.tools.common.flicker.FlickerConfig
+import android.tools.common.flicker.annotation.ExpectedScenarios
+import android.tools.common.flicker.annotation.FlickerConfigProvider
+import android.tools.common.flicker.config.FlickerConfig
+import android.tools.common.flicker.config.FlickerServiceConfig
+import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.DismissSplitScreenByDivider
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class DismissSplitScreenByDividerGesturalNavPortrait :
+    DismissSplitScreenByDivider(Rotation.ROTATION_0) {
+
+    @ExpectedScenarios(["SPLIT_SCREEN_EXIT"])
+    @Test
+    override fun dismissSplitScreenByDivider() = super.dismissSplitScreenByDivider()
+
+    companion object {
+        @JvmStatic
+        @FlickerConfigProvider
+        fun flickerConfigProvider(): FlickerConfig =
+            FlickerConfig().use(FlickerServiceConfig.DEFAULT)
+    }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/DismissSplitScreenByGoHomeGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/DismissSplitScreenByGoHomeGesturalNavLandscape.kt
new file mode 100644
index 0000000..aa35237
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/DismissSplitScreenByGoHomeGesturalNavLandscape.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.wm.shell.flicker.service.splitscreen.flicker
+
+import android.tools.common.Rotation
+import android.tools.common.flicker.FlickerConfig
+import android.tools.common.flicker.annotation.ExpectedScenarios
+import android.tools.common.flicker.annotation.FlickerConfigProvider
+import android.tools.common.flicker.config.FlickerConfig
+import android.tools.common.flicker.config.FlickerServiceConfig
+import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.DismissSplitScreenByGoHome
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class DismissSplitScreenByGoHomeGesturalNavLandscape :
+    DismissSplitScreenByGoHome(Rotation.ROTATION_90) {
+
+    @ExpectedScenarios(["SPLIT_SCREEN_EXIT"])
+    @Test
+    override fun dismissSplitScreenByGoHome() = super.dismissSplitScreenByGoHome()
+
+    companion object {
+        @JvmStatic
+        @FlickerConfigProvider
+        fun flickerConfigProvider(): FlickerConfig =
+            FlickerConfig().use(FlickerServiceConfig.DEFAULT)
+    }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/DismissSplitScreenByGoHomeGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/DismissSplitScreenByGoHomeGesturalNavPortrait.kt
new file mode 100644
index 0000000..e195360
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/DismissSplitScreenByGoHomeGesturalNavPortrait.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.wm.shell.flicker.service.splitscreen.flicker
+
+import android.tools.common.Rotation
+import android.tools.common.flicker.FlickerConfig
+import android.tools.common.flicker.annotation.ExpectedScenarios
+import android.tools.common.flicker.annotation.FlickerConfigProvider
+import android.tools.common.flicker.config.FlickerConfig
+import android.tools.common.flicker.config.FlickerServiceConfig
+import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.DismissSplitScreenByGoHome
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class DismissSplitScreenByGoHomeGesturalNavPortrait :
+    DismissSplitScreenByGoHome(Rotation.ROTATION_0) {
+
+    @ExpectedScenarios(["SPLIT_SCREEN_EXIT"])
+    @Test
+    override fun dismissSplitScreenByGoHome() = super.dismissSplitScreenByGoHome()
+
+    companion object {
+        @JvmStatic
+        @FlickerConfigProvider
+        fun flickerConfigProvider(): FlickerConfig =
+            FlickerConfig().use(FlickerServiceConfig.DEFAULT)
+    }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/DragDividerToResizeGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/DragDividerToResizeGesturalNavLandscape.kt
new file mode 100644
index 0000000..c1b3aad
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/DragDividerToResizeGesturalNavLandscape.kt
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.service.splitscreen.flicker
+
+import android.tools.common.Rotation
+import android.tools.common.flicker.FlickerConfig
+import android.tools.common.flicker.annotation.ExpectedScenarios
+import android.tools.common.flicker.annotation.FlickerConfigProvider
+import android.tools.common.flicker.config.FlickerConfig
+import android.tools.common.flicker.config.FlickerServiceConfig
+import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.DragDividerToResize
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class DragDividerToResizeGesturalNavLandscape : DragDividerToResize(Rotation.ROTATION_90) {
+
+    @ExpectedScenarios(["SPLIT_SCREEN_RESIZE"])
+    @Test
+    override fun dragDividerToResize() = super.dragDividerToResize()
+
+    companion object {
+        @JvmStatic
+        @FlickerConfigProvider
+        fun flickerConfigProvider(): FlickerConfig =
+            FlickerConfig().use(FlickerServiceConfig.DEFAULT)
+    }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/DragDividerToResizeGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/DragDividerToResizeGesturalNavPortrait.kt
new file mode 100644
index 0000000..c6e2e85
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/DragDividerToResizeGesturalNavPortrait.kt
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.service.splitscreen.flicker
+
+import android.tools.common.Rotation
+import android.tools.common.flicker.FlickerConfig
+import android.tools.common.flicker.annotation.ExpectedScenarios
+import android.tools.common.flicker.annotation.FlickerConfigProvider
+import android.tools.common.flicker.config.FlickerConfig
+import android.tools.common.flicker.config.FlickerServiceConfig
+import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.DragDividerToResize
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class DragDividerToResizeGesturalNavPortrait : DragDividerToResize(Rotation.ROTATION_0) {
+
+    @ExpectedScenarios(["SPLIT_SCREEN_RESIZE"])
+    @Test
+    override fun dragDividerToResize() = super.dragDividerToResize()
+
+    companion object {
+        @JvmStatic
+        @FlickerConfigProvider
+        fun flickerConfigProvider(): FlickerConfig =
+            FlickerConfig().use(FlickerServiceConfig.DEFAULT)
+    }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromAllAppsGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromAllAppsGesturalNavLandscape.kt
new file mode 100644
index 0000000..5f771c7
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromAllAppsGesturalNavLandscape.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.wm.shell.flicker.service.splitscreen.flicker
+
+import android.tools.common.Rotation
+import android.tools.common.flicker.FlickerConfig
+import android.tools.common.flicker.annotation.ExpectedScenarios
+import android.tools.common.flicker.annotation.FlickerConfigProvider
+import android.tools.common.flicker.config.FlickerConfig
+import android.tools.common.flicker.config.FlickerServiceConfig
+import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenByDragFromAllApps
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class EnterSplitScreenByDragFromAllAppsGesturalNavLandscape :
+    EnterSplitScreenByDragFromAllApps(Rotation.ROTATION_90) {
+
+    @ExpectedScenarios(["SPLIT_SCREEN_ENTER"])
+    @Test
+    override fun enterSplitScreenByDragFromAllApps() = super.enterSplitScreenByDragFromAllApps()
+
+    companion object {
+        @JvmStatic
+        @FlickerConfigProvider
+        fun flickerConfigProvider(): FlickerConfig =
+            FlickerConfig().use(FlickerServiceConfig.DEFAULT)
+    }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromAllAppsGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromAllAppsGesturalNavPortrait.kt
new file mode 100644
index 0000000..729a401
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromAllAppsGesturalNavPortrait.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.wm.shell.flicker.service.splitscreen.flicker
+
+import android.tools.common.Rotation
+import android.tools.common.flicker.FlickerConfig
+import android.tools.common.flicker.annotation.ExpectedScenarios
+import android.tools.common.flicker.annotation.FlickerConfigProvider
+import android.tools.common.flicker.config.FlickerConfig
+import android.tools.common.flicker.config.FlickerServiceConfig
+import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenByDragFromAllApps
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class EnterSplitScreenByDragFromAllAppsGesturalNavPortrait :
+    EnterSplitScreenByDragFromAllApps(Rotation.ROTATION_0) {
+
+    @ExpectedScenarios(["SPLIT_SCREEN_ENTER"])
+    @Test
+    override fun enterSplitScreenByDragFromAllApps() = super.enterSplitScreenByDragFromAllApps()
+
+    companion object {
+        @JvmStatic
+        @FlickerConfigProvider
+        fun flickerConfigProvider(): FlickerConfig =
+            FlickerConfig().use(FlickerServiceConfig.DEFAULT)
+    }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromNotificationGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromNotificationGesturalNavLandscape.kt
new file mode 100644
index 0000000..6e4cf9f
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromNotificationGesturalNavLandscape.kt
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.service.splitscreen.flicker
+
+import android.tools.common.Rotation
+import android.tools.common.flicker.FlickerConfig
+import android.tools.common.flicker.annotation.ExpectedScenarios
+import android.tools.common.flicker.annotation.FlickerConfigProvider
+import android.tools.common.flicker.config.FlickerConfig
+import android.tools.common.flicker.config.FlickerServiceConfig
+import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenByDragFromNotification
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class EnterSplitScreenByDragFromNotificationGesturalNavLandscape :
+    EnterSplitScreenByDragFromNotification(Rotation.ROTATION_90) {
+
+    @ExpectedScenarios(["SPLIT_SCREEN_ENTER"])
+    @Test
+    override fun enterSplitScreenByDragFromNotification() =
+        super.enterSplitScreenByDragFromNotification()
+
+    companion object {
+        @JvmStatic
+        @FlickerConfigProvider
+        fun flickerConfigProvider(): FlickerConfig =
+            FlickerConfig().use(FlickerServiceConfig.DEFAULT)
+    }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromNotificationGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromNotificationGesturalNavPortrait.kt
new file mode 100644
index 0000000..cc28702
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromNotificationGesturalNavPortrait.kt
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.service.splitscreen.flicker
+
+import android.tools.common.Rotation
+import android.tools.common.flicker.FlickerConfig
+import android.tools.common.flicker.annotation.ExpectedScenarios
+import android.tools.common.flicker.annotation.FlickerConfigProvider
+import android.tools.common.flicker.config.FlickerConfig
+import android.tools.common.flicker.config.FlickerServiceConfig
+import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenByDragFromNotification
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class EnterSplitScreenByDragFromNotificationGesturalNavPortrait :
+    EnterSplitScreenByDragFromNotification(Rotation.ROTATION_0) {
+
+    @ExpectedScenarios(["SPLIT_SCREEN_ENTER"])
+    @Test
+    override fun enterSplitScreenByDragFromNotification() =
+        super.enterSplitScreenByDragFromNotification()
+
+    companion object {
+        @JvmStatic
+        @FlickerConfigProvider
+        fun flickerConfigProvider(): FlickerConfig =
+            FlickerConfig().use(FlickerServiceConfig.DEFAULT)
+    }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromShortcutGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromShortcutGesturalNavLandscape.kt
new file mode 100644
index 0000000..736604f
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromShortcutGesturalNavLandscape.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.wm.shell.flicker.service.splitscreen.flicker
+
+import android.tools.common.Rotation
+import android.tools.common.flicker.FlickerConfig
+import android.tools.common.flicker.annotation.ExpectedScenarios
+import android.tools.common.flicker.annotation.FlickerConfigProvider
+import android.tools.common.flicker.config.FlickerConfig
+import android.tools.common.flicker.config.FlickerServiceConfig
+import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenByDragFromShortcut
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class EnterSplitScreenByDragFromShortcutGesturalNavLandscape :
+    EnterSplitScreenByDragFromShortcut(Rotation.ROTATION_90) {
+
+    @ExpectedScenarios(["SPLIT_SCREEN_ENTER"])
+    @Test
+    override fun enterSplitScreenByDragFromShortcut() = super.enterSplitScreenByDragFromShortcut()
+
+    companion object {
+        @JvmStatic
+        @FlickerConfigProvider
+        fun flickerConfigProvider(): FlickerConfig =
+            FlickerConfig().use(FlickerServiceConfig.DEFAULT)
+    }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromShortcutGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromShortcutGesturalNavPortrait.kt
new file mode 100644
index 0000000..8df8dfa
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromShortcutGesturalNavPortrait.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.wm.shell.flicker.service.splitscreen.flicker
+
+import android.tools.common.Rotation
+import android.tools.common.flicker.FlickerConfig
+import android.tools.common.flicker.annotation.ExpectedScenarios
+import android.tools.common.flicker.annotation.FlickerConfigProvider
+import android.tools.common.flicker.config.FlickerConfig
+import android.tools.common.flicker.config.FlickerServiceConfig
+import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenByDragFromShortcut
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class EnterSplitScreenByDragFromShortcutGesturalNavPortrait :
+    EnterSplitScreenByDragFromShortcut(Rotation.ROTATION_0) {
+
+    @ExpectedScenarios(["SPLIT_SCREEN_ENTER"])
+    @Test
+    override fun enterSplitScreenByDragFromShortcut() = super.enterSplitScreenByDragFromShortcut()
+
+    companion object {
+        @JvmStatic
+        @FlickerConfigProvider
+        fun flickerConfigProvider(): FlickerConfig =
+            FlickerConfig().use(FlickerServiceConfig.DEFAULT)
+    }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromTaskbarGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromTaskbarGesturalNavLandscape.kt
new file mode 100644
index 0000000..378f055
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromTaskbarGesturalNavLandscape.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.wm.shell.flicker.service.splitscreen.flicker
+
+import android.tools.common.Rotation
+import android.tools.common.flicker.FlickerConfig
+import android.tools.common.flicker.annotation.ExpectedScenarios
+import android.tools.common.flicker.annotation.FlickerConfigProvider
+import android.tools.common.flicker.config.FlickerConfig
+import android.tools.common.flicker.config.FlickerServiceConfig
+import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenByDragFromTaskbar
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class EnterSplitScreenByDragFromTaskbarGesturalNavLandscape :
+    EnterSplitScreenByDragFromTaskbar(Rotation.ROTATION_90) {
+
+    @ExpectedScenarios(["SPLIT_SCREEN_ENTER"])
+    @Test
+    override fun enterSplitScreenByDragFromTaskbar() = super.enterSplitScreenByDragFromTaskbar()
+
+    companion object {
+        @JvmStatic
+        @FlickerConfigProvider
+        fun flickerConfigProvider(): FlickerConfig =
+            FlickerConfig().use(FlickerServiceConfig.DEFAULT)
+    }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromTaskbarGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromTaskbarGesturalNavPortrait.kt
new file mode 100644
index 0000000..b33d262
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromTaskbarGesturalNavPortrait.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.wm.shell.flicker.service.splitscreen.flicker
+
+import android.tools.common.Rotation
+import android.tools.common.flicker.FlickerConfig
+import android.tools.common.flicker.annotation.ExpectedScenarios
+import android.tools.common.flicker.annotation.FlickerConfigProvider
+import android.tools.common.flicker.config.FlickerConfig
+import android.tools.common.flicker.config.FlickerServiceConfig
+import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenByDragFromTaskbar
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class EnterSplitScreenByDragFromTaskbarGesturalNavPortrait :
+    EnterSplitScreenByDragFromTaskbar(Rotation.ROTATION_0) {
+
+    @ExpectedScenarios(["SPLIT_SCREEN_ENTER"])
+    @Test
+    override fun enterSplitScreenByDragFromTaskbar() = super.enterSplitScreenByDragFromTaskbar()
+
+    companion object {
+        @JvmStatic
+        @FlickerConfigProvider
+        fun flickerConfigProvider(): FlickerConfig =
+            FlickerConfig().use(FlickerServiceConfig.DEFAULT)
+    }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenFromOverviewGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenFromOverviewGesturalNavLandscape.kt
new file mode 100644
index 0000000..b1d3858
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenFromOverviewGesturalNavLandscape.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.wm.shell.flicker.service.splitscreen.flicker
+
+import android.tools.common.Rotation
+import android.tools.common.flicker.FlickerConfig
+import android.tools.common.flicker.annotation.ExpectedScenarios
+import android.tools.common.flicker.annotation.FlickerConfigProvider
+import android.tools.common.flicker.config.FlickerConfig
+import android.tools.common.flicker.config.FlickerServiceConfig
+import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenFromOverview
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class EnterSplitScreenFromOverviewGesturalNavLandscape :
+    EnterSplitScreenFromOverview(Rotation.ROTATION_90) {
+
+    @ExpectedScenarios(["SPLIT_SCREEN_ENTER"])
+    @Test
+    override fun enterSplitScreenFromOverview() = super.enterSplitScreenFromOverview()
+
+    companion object {
+        @JvmStatic
+        @FlickerConfigProvider
+        fun flickerConfigProvider(): FlickerConfig =
+            FlickerConfig().use(FlickerServiceConfig.DEFAULT)
+    }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenFromOverviewGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenFromOverviewGesturalNavPortrait.kt
new file mode 100644
index 0000000..6d824c7
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenFromOverviewGesturalNavPortrait.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.wm.shell.flicker.service.splitscreen.flicker
+
+import android.tools.common.Rotation
+import android.tools.common.flicker.FlickerConfig
+import android.tools.common.flicker.annotation.ExpectedScenarios
+import android.tools.common.flicker.annotation.FlickerConfigProvider
+import android.tools.common.flicker.config.FlickerConfig
+import android.tools.common.flicker.config.FlickerServiceConfig
+import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenFromOverview
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class EnterSplitScreenFromOverviewGesturalNavPortrait :
+    EnterSplitScreenFromOverview(Rotation.ROTATION_0) {
+
+    @ExpectedScenarios(["SPLIT_SCREEN_ENTER"])
+    @Test
+    override fun enterSplitScreenFromOverview() = super.enterSplitScreenFromOverview()
+
+    companion object {
+        @JvmStatic
+        @FlickerConfigProvider
+        fun flickerConfigProvider(): FlickerConfig =
+            FlickerConfig().use(FlickerServiceConfig.DEFAULT)
+    }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchAppByDoubleTapDividerGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchAppByDoubleTapDividerGesturalNavLandscape.kt
new file mode 100644
index 0000000..f1d3d0c
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchAppByDoubleTapDividerGesturalNavLandscape.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.wm.shell.flicker.service.splitscreen.flicker
+
+import android.tools.common.Rotation
+import android.tools.common.flicker.FlickerConfig
+import android.tools.common.flicker.annotation.ExpectedScenarios
+import android.tools.common.flicker.annotation.FlickerConfigProvider
+import android.tools.common.flicker.config.FlickerConfig
+import android.tools.common.flicker.config.FlickerServiceConfig
+import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchAppByDoubleTapDivider
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class SwitchAppByDoubleTapDividerGesturalNavLandscape :
+    SwitchAppByDoubleTapDivider(Rotation.ROTATION_90) {
+
+    @ExpectedScenarios([])
+    @Test
+    override fun switchAppByDoubleTapDivider() = super.switchAppByDoubleTapDivider()
+
+    companion object {
+        @JvmStatic
+        @FlickerConfigProvider
+        fun flickerConfigProvider(): FlickerConfig =
+            FlickerConfig().use(FlickerServiceConfig.DEFAULT)
+    }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchAppByDoubleTapDividerGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchAppByDoubleTapDividerGesturalNavPortrait.kt
new file mode 100644
index 0000000..a867bac
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchAppByDoubleTapDividerGesturalNavPortrait.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.wm.shell.flicker.service.splitscreen.flicker
+
+import android.tools.common.Rotation
+import android.tools.common.flicker.FlickerConfig
+import android.tools.common.flicker.annotation.ExpectedScenarios
+import android.tools.common.flicker.annotation.FlickerConfigProvider
+import android.tools.common.flicker.config.FlickerConfig
+import android.tools.common.flicker.config.FlickerServiceConfig
+import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchAppByDoubleTapDivider
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class SwitchAppByDoubleTapDividerGesturalNavPortrait :
+    SwitchAppByDoubleTapDivider(Rotation.ROTATION_0) {
+
+    @ExpectedScenarios([])
+    @Test
+    override fun switchAppByDoubleTapDivider() = super.switchAppByDoubleTapDivider()
+
+    companion object {
+        @JvmStatic
+        @FlickerConfigProvider
+        fun flickerConfigProvider(): FlickerConfig =
+            FlickerConfig().use(FlickerServiceConfig.DEFAULT)
+    }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBackToSplitFromAnotherAppGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBackToSplitFromAnotherAppGesturalNavLandscape.kt
new file mode 100644
index 0000000..76247ba
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBackToSplitFromAnotherAppGesturalNavLandscape.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.wm.shell.flicker.service.splitscreen.flicker
+
+import android.tools.common.Rotation
+import android.tools.common.flicker.FlickerConfig
+import android.tools.common.flicker.annotation.ExpectedScenarios
+import android.tools.common.flicker.annotation.FlickerConfigProvider
+import android.tools.common.flicker.config.FlickerConfig
+import android.tools.common.flicker.config.FlickerServiceConfig
+import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchBackToSplitFromAnotherApp
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class SwitchBackToSplitFromAnotherAppGesturalNavLandscape :
+    SwitchBackToSplitFromAnotherApp(Rotation.ROTATION_90) {
+
+    @ExpectedScenarios(["QUICKSWITCH"])
+    @Test
+    override fun switchBackToSplitFromAnotherApp() = super.switchBackToSplitFromAnotherApp()
+
+    companion object {
+        @JvmStatic
+        @FlickerConfigProvider
+        fun flickerConfigProvider(): FlickerConfig =
+            FlickerConfig().use(FlickerServiceConfig.DEFAULT)
+    }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBackToSplitFromAnotherAppGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBackToSplitFromAnotherAppGesturalNavPortrait.kt
new file mode 100644
index 0000000..e179da8
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBackToSplitFromAnotherAppGesturalNavPortrait.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.wm.shell.flicker.service.splitscreen.flicker
+
+import android.tools.common.Rotation
+import android.tools.common.flicker.FlickerConfig
+import android.tools.common.flicker.annotation.ExpectedScenarios
+import android.tools.common.flicker.annotation.FlickerConfigProvider
+import android.tools.common.flicker.config.FlickerConfig
+import android.tools.common.flicker.config.FlickerServiceConfig
+import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchBackToSplitFromAnotherApp
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class SwitchBackToSplitFromAnotherAppGesturalNavPortrait :
+    SwitchBackToSplitFromAnotherApp(Rotation.ROTATION_0) {
+
+    @ExpectedScenarios(["QUICKSWITCH"])
+    @Test
+    override fun switchBackToSplitFromAnotherApp() = super.switchBackToSplitFromAnotherApp()
+
+    companion object {
+        @JvmStatic
+        @FlickerConfigProvider
+        fun flickerConfigProvider(): FlickerConfig =
+            FlickerConfig().use(FlickerServiceConfig.DEFAULT)
+    }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBackToSplitFromHomeGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBackToSplitFromHomeGesturalNavLandscape.kt
new file mode 100644
index 0000000..20f554f
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBackToSplitFromHomeGesturalNavLandscape.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.wm.shell.flicker.service.splitscreen.flicker
+
+import android.tools.common.Rotation
+import android.tools.common.flicker.FlickerConfig
+import android.tools.common.flicker.annotation.ExpectedScenarios
+import android.tools.common.flicker.annotation.FlickerConfigProvider
+import android.tools.common.flicker.config.FlickerConfig
+import android.tools.common.flicker.config.FlickerServiceConfig
+import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchBackToSplitFromHome
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class SwitchBackToSplitFromHomeGesturalNavLandscape :
+    SwitchBackToSplitFromHome(Rotation.ROTATION_90) {
+
+    @ExpectedScenarios(["QUICKSWITCH"])
+    @Test
+    override fun switchBackToSplitFromHome() = super.switchBackToSplitFromHome()
+
+    companion object {
+        @JvmStatic
+        @FlickerConfigProvider
+        fun flickerConfigProvider(): FlickerConfig =
+            FlickerConfig().use(FlickerServiceConfig.DEFAULT)
+    }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBackToSplitFromHomeGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBackToSplitFromHomeGesturalNavPortrait.kt
new file mode 100644
index 0000000..f7776ee
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBackToSplitFromHomeGesturalNavPortrait.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.wm.shell.flicker.service.splitscreen.flicker
+
+import android.tools.common.Rotation
+import android.tools.common.flicker.FlickerConfig
+import android.tools.common.flicker.annotation.ExpectedScenarios
+import android.tools.common.flicker.annotation.FlickerConfigProvider
+import android.tools.common.flicker.config.FlickerConfig
+import android.tools.common.flicker.config.FlickerServiceConfig
+import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchBackToSplitFromHome
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class SwitchBackToSplitFromHomeGesturalNavPortrait :
+    SwitchBackToSplitFromHome(Rotation.ROTATION_0) {
+
+    @ExpectedScenarios(["QUICKSWITCH"])
+    @Test
+    override fun switchBackToSplitFromHome() = super.switchBackToSplitFromHome()
+
+    companion object {
+        @JvmStatic
+        @FlickerConfigProvider
+        fun flickerConfigProvider(): FlickerConfig =
+            FlickerConfig().use(FlickerServiceConfig.DEFAULT)
+    }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBackToSplitFromRecentGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBackToSplitFromRecentGesturalNavLandscape.kt
new file mode 100644
index 0000000..00f6073
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBackToSplitFromRecentGesturalNavLandscape.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.wm.shell.flicker.service.splitscreen.flicker
+
+import android.tools.common.Rotation
+import android.tools.common.flicker.FlickerConfig
+import android.tools.common.flicker.annotation.ExpectedScenarios
+import android.tools.common.flicker.annotation.FlickerConfigProvider
+import android.tools.common.flicker.config.FlickerConfig
+import android.tools.common.flicker.config.FlickerServiceConfig
+import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchBackToSplitFromRecent
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class SwitchBackToSplitFromRecentGesturalNavLandscape :
+    SwitchBackToSplitFromRecent(Rotation.ROTATION_90) {
+
+    @ExpectedScenarios(["QUICKSWITCH"])
+    @Test
+    override fun switchBackToSplitFromRecent() = super.switchBackToSplitFromRecent()
+
+    companion object {
+        @JvmStatic
+        @FlickerConfigProvider
+        fun flickerConfigProvider(): FlickerConfig =
+            FlickerConfig().use(FlickerServiceConfig.DEFAULT)
+    }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBackToSplitFromRecentGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBackToSplitFromRecentGesturalNavPortrait.kt
new file mode 100644
index 0000000..b3340e7
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBackToSplitFromRecentGesturalNavPortrait.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.wm.shell.flicker.service.splitscreen.flicker
+
+import android.tools.common.Rotation
+import android.tools.common.flicker.FlickerConfig
+import android.tools.common.flicker.annotation.ExpectedScenarios
+import android.tools.common.flicker.annotation.FlickerConfigProvider
+import android.tools.common.flicker.config.FlickerConfig
+import android.tools.common.flicker.config.FlickerServiceConfig
+import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchBackToSplitFromRecent
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class SwitchBackToSplitFromRecentGesturalNavPortrait :
+    SwitchBackToSplitFromRecent(Rotation.ROTATION_0) {
+
+    @ExpectedScenarios(["QUICKSWITCH"])
+    @Test
+    override fun switchBackToSplitFromRecent() = super.switchBackToSplitFromRecent()
+
+    companion object {
+        @JvmStatic
+        @FlickerConfigProvider
+        fun flickerConfigProvider(): FlickerConfig =
+            FlickerConfig().use(FlickerServiceConfig.DEFAULT)
+    }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBetweenSplitPairsGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBetweenSplitPairsGesturalNavLandscape.kt
new file mode 100644
index 0000000..3da61e5
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBetweenSplitPairsGesturalNavLandscape.kt
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.service.splitscreen.flicker
+
+import android.tools.common.Rotation
+import android.tools.common.flicker.FlickerConfig
+import android.tools.common.flicker.annotation.ExpectedScenarios
+import android.tools.common.flicker.annotation.FlickerConfigProvider
+import android.tools.common.flicker.config.FlickerConfig
+import android.tools.common.flicker.config.FlickerServiceConfig
+import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchBetweenSplitPairs
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class SwitchBetweenSplitPairsGesturalNavLandscape : SwitchBetweenSplitPairs(Rotation.ROTATION_90) {
+
+    @ExpectedScenarios(["QUICKSWITCH"])
+    @Test
+    override fun switchBetweenSplitPairs() = super.switchBetweenSplitPairs()
+
+    companion object {
+        @JvmStatic
+        @FlickerConfigProvider
+        fun flickerConfigProvider(): FlickerConfig =
+            FlickerConfig().use(FlickerServiceConfig.DEFAULT)
+    }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBetweenSplitPairsGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBetweenSplitPairsGesturalNavPortrait.kt
new file mode 100644
index 0000000..627ae18
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBetweenSplitPairsGesturalNavPortrait.kt
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.service.splitscreen.flicker
+
+import android.tools.common.Rotation
+import android.tools.common.flicker.FlickerConfig
+import android.tools.common.flicker.annotation.ExpectedScenarios
+import android.tools.common.flicker.annotation.FlickerConfigProvider
+import android.tools.common.flicker.config.FlickerConfig
+import android.tools.common.flicker.config.FlickerServiceConfig
+import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchBetweenSplitPairs
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class SwitchBetweenSplitPairsGesturalNavPortrait : SwitchBetweenSplitPairs(Rotation.ROTATION_0) {
+
+    @ExpectedScenarios(["QUICKSWITCH"])
+    @Test
+    override fun switchBetweenSplitPairs() = super.switchBetweenSplitPairs()
+
+    companion object {
+        @JvmStatic
+        @FlickerConfigProvider
+        fun flickerConfigProvider(): FlickerConfig =
+            FlickerConfig().use(FlickerServiceConfig.DEFAULT)
+    }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/UnlockKeyguardToSplitScreenGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/UnlockKeyguardToSplitScreenGesturalNavLandscape.kt
new file mode 100644
index 0000000..7cbc1c3
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/UnlockKeyguardToSplitScreenGesturalNavLandscape.kt
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.service.splitscreen.flicker
+
+import android.tools.common.flicker.annotation.ExpectedScenarios
+import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.UnlockKeyguardToSplitScreen
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class UnlockKeyguardToSplitScreenGesturalNavLandscape : UnlockKeyguardToSplitScreen() {
+
+    @ExpectedScenarios(["QUICKSWITCH"])
+    @Test
+    override fun unlockKeyguardToSplitScreen() = super.unlockKeyguardToSplitScreen()
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/UnlockKeyguardToSplitScreenGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/UnlockKeyguardToSplitScreenGesturalNavPortrait.kt
new file mode 100644
index 0000000..2eb81e0
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/UnlockKeyguardToSplitScreenGesturalNavPortrait.kt
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.service.splitscreen.flicker
+
+import android.tools.common.flicker.annotation.ExpectedScenarios
+import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.UnlockKeyguardToSplitScreen
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class UnlockKeyguardToSplitScreenGesturalNavPortrait : UnlockKeyguardToSplitScreen() {
+
+    @ExpectedScenarios(["QUICKSWITCH"])
+    @Test
+    override fun unlockKeyguardToSplitScreen() = super.unlockKeyguardToSplitScreen()
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/CopyContentInSplit.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/CopyContentInSplit.kt
new file mode 100644
index 0000000..76ad6b9
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/CopyContentInSplit.kt
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.service.splitscreen.scenarios
+
+import android.app.Instrumentation
+import android.tools.common.NavBar
+import android.tools.common.Rotation
+import android.tools.device.traces.parsers.WindowManagerStateHelper
+import androidx.test.platform.app.InstrumentationRegistry
+import androidx.test.uiautomator.UiDevice
+import com.android.launcher3.tapl.LauncherInstrumentation
+import org.junit.After
+import org.junit.Before
+import org.junit.Ignore
+import org.junit.Rule
+import org.junit.Test
+
+@Ignore("Base Test Class")
+abstract class CopyContentInSplit
+@JvmOverloads
+constructor(val rotation: Rotation = Rotation.ROTATION_0) {
+
+    private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
+    private val tapl = LauncherInstrumentation()
+    private val wmHelper = WindowManagerStateHelper(instrumentation)
+    private val device = UiDevice.getInstance(instrumentation)
+    private val primaryApp = SplitScreenUtils.getPrimary(instrumentation)
+    private val secondaryApp = SplitScreenUtils.getSecondary(instrumentation)
+    private val textEditApp = SplitScreenUtils.getIme(instrumentation)
+
+    @Rule
+    @JvmField
+    val testSetupRule = SplitScreenUtils.testSetupRule({ NavBar.MODE_GESTURAL }, { rotation })
+
+    @Before
+    fun setup() {
+        tapl.setEnableRotation(true)
+        tapl.setExpectedRotation(rotation.value)
+
+        SplitScreenUtils.enterSplit(wmHelper, tapl, device, primaryApp, textEditApp)
+    }
+
+    @Test
+    open fun copyContentInSplit() {
+        SplitScreenUtils.copyContentInSplit(instrumentation, device, primaryApp, textEditApp)
+    }
+
+    @After
+    fun teardown() {
+        primaryApp.exit(wmHelper)
+        secondaryApp.exit(wmHelper)
+    }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/DismissSplitScreenByDivider.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/DismissSplitScreenByDivider.kt
new file mode 100644
index 0000000..25182b4
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/DismissSplitScreenByDivider.kt
@@ -0,0 +1,80 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.service.splitscreen.scenarios
+
+import android.app.Instrumentation
+import android.tools.common.NavBar
+import android.tools.common.Rotation
+import android.tools.device.traces.parsers.WindowManagerStateHelper
+import androidx.test.platform.app.InstrumentationRegistry
+import androidx.test.uiautomator.UiDevice
+import com.android.launcher3.tapl.LauncherInstrumentation
+import org.junit.After
+import org.junit.Before
+import org.junit.Ignore
+import org.junit.Rule
+import org.junit.Test
+
+@Ignore("Base Test Class")
+abstract class DismissSplitScreenByDivider
+@JvmOverloads
+constructor(val rotation: Rotation = Rotation.ROTATION_0) {
+    private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
+    private val tapl = LauncherInstrumentation()
+    private val wmHelper = WindowManagerStateHelper(instrumentation)
+    private val device = UiDevice.getInstance(instrumentation)
+    private val primaryApp = SplitScreenUtils.getPrimary(instrumentation)
+    private val secondaryApp = SplitScreenUtils.getSecondary(instrumentation)
+
+    @Rule
+    @JvmField
+    val testSetupRule = SplitScreenUtils.testSetupRule({ NavBar.MODE_GESTURAL }, { rotation })
+
+    @Before
+    fun setup() {
+        tapl.setEnableRotation(true)
+        tapl.setExpectedRotation(rotation.value)
+
+        SplitScreenUtils.enterSplit(wmHelper, tapl, device, primaryApp, secondaryApp)
+    }
+
+    @Test
+    open fun dismissSplitScreenByDivider() {
+        if (tapl.isTablet) {
+            SplitScreenUtils.dragDividerToDismissSplit(
+                device,
+                wmHelper,
+                dragToRight = false,
+                dragToBottom = true
+            )
+        } else {
+            SplitScreenUtils.dragDividerToDismissSplit(
+                device,
+                wmHelper,
+                dragToRight = true,
+                dragToBottom = true
+            )
+        }
+        wmHelper.StateSyncBuilder().withFullScreenApp(secondaryApp).waitForAndVerify()
+    }
+
+    @After
+    fun teardown() {
+        primaryApp.exit(wmHelper)
+        secondaryApp.exit(wmHelper)
+    }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/DismissSplitScreenByGoHome.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/DismissSplitScreenByGoHome.kt
new file mode 100644
index 0000000..000b628
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/DismissSplitScreenByGoHome.kt
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.service.splitscreen.scenarios
+
+import android.app.Instrumentation
+import android.tools.common.NavBar
+import android.tools.common.Rotation
+import android.tools.device.traces.parsers.WindowManagerStateHelper
+import androidx.test.platform.app.InstrumentationRegistry
+import androidx.test.uiautomator.UiDevice
+import com.android.launcher3.tapl.LauncherInstrumentation
+import org.junit.After
+import org.junit.Before
+import org.junit.Ignore
+import org.junit.Rule
+import org.junit.Test
+
+@Ignore("Base Test Class")
+abstract class DismissSplitScreenByGoHome
+@JvmOverloads
+constructor(val rotation: Rotation = Rotation.ROTATION_0) {
+    private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
+    private val tapl = LauncherInstrumentation()
+    private val wmHelper = WindowManagerStateHelper(instrumentation)
+    private val device = UiDevice.getInstance(instrumentation)
+    private val primaryApp = SplitScreenUtils.getPrimary(instrumentation)
+    private val secondaryApp = SplitScreenUtils.getSecondary(instrumentation)
+
+    @Rule
+    @JvmField
+    val testSetupRule = SplitScreenUtils.testSetupRule({ NavBar.MODE_GESTURAL }, { rotation })
+
+    @Before
+    fun setup() {
+        tapl.setEnableRotation(true)
+        tapl.setExpectedRotation(rotation.value)
+
+        SplitScreenUtils.enterSplit(wmHelper, tapl, device, primaryApp, secondaryApp)
+    }
+
+    @Test
+    open fun dismissSplitScreenByGoHome() {
+        tapl.goHome()
+        wmHelper.StateSyncBuilder().withHomeActivityVisible().waitForAndVerify()
+    }
+
+    @After
+    fun teardown() {
+        primaryApp.exit(wmHelper)
+        secondaryApp.exit(wmHelper)
+    }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/DragDividerToResize.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/DragDividerToResize.kt
new file mode 100644
index 0000000..dd9ff3c
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/DragDividerToResize.kt
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.service.splitscreen.scenarios
+
+import android.app.Instrumentation
+import android.tools.common.NavBar
+import android.tools.common.Rotation
+import android.tools.device.traces.parsers.WindowManagerStateHelper
+import androidx.test.platform.app.InstrumentationRegistry
+import androidx.test.uiautomator.UiDevice
+import com.android.launcher3.tapl.LauncherInstrumentation
+import org.junit.After
+import org.junit.Before
+import org.junit.Ignore
+import org.junit.Rule
+import org.junit.Test
+
+@Ignore("Base Test Class")
+abstract class DragDividerToResize
+@JvmOverloads
+constructor(val rotation: Rotation = Rotation.ROTATION_0) {
+    private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
+    private val tapl = LauncherInstrumentation()
+    private val wmHelper = WindowManagerStateHelper(instrumentation)
+    private val device = UiDevice.getInstance(instrumentation)
+    private val primaryApp = SplitScreenUtils.getPrimary(instrumentation)
+    private val secondaryApp = SplitScreenUtils.getSecondary(instrumentation)
+
+    @Rule
+    @JvmField
+    val testSetupRule = SplitScreenUtils.testSetupRule({ NavBar.MODE_GESTURAL }, { rotation })
+
+    @Before
+    fun setup() {
+        tapl.setEnableRotation(true)
+        tapl.setExpectedRotation(rotation.value)
+
+        SplitScreenUtils.enterSplit(wmHelper, tapl, device, primaryApp, secondaryApp)
+    }
+
+    @Test
+    open fun dragDividerToResize() {
+        SplitScreenUtils.dragDividerToResizeAndWait(device, wmHelper)
+    }
+
+    @After
+    fun teardown() {
+        primaryApp.exit(wmHelper)
+        secondaryApp.exit(wmHelper)
+    }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromAllApps.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromAllApps.kt
new file mode 100644
index 0000000..4bbb9aa
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromAllApps.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.wm.shell.flicker.service.splitscreen.scenarios
+
+import android.app.Instrumentation
+import android.tools.common.NavBar
+import android.tools.common.Rotation
+import android.tools.device.traces.parsers.WindowManagerStateHelper
+import androidx.test.platform.app.InstrumentationRegistry
+import androidx.test.uiautomator.UiDevice
+import com.android.launcher3.tapl.LauncherInstrumentation
+import org.junit.After
+import org.junit.Before
+import org.junit.Ignore
+import org.junit.Rule
+import org.junit.Test
+
+@Ignore("Base Test Class")
+abstract class EnterSplitScreenByDragFromAllApps
+@JvmOverloads
+constructor(val rotation: Rotation = Rotation.ROTATION_0) {
+    private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
+    private val tapl = LauncherInstrumentation()
+    private val wmHelper = WindowManagerStateHelper(instrumentation)
+    private val device = UiDevice.getInstance(instrumentation)
+    private val primaryApp = SplitScreenUtils.getPrimary(instrumentation)
+    private val secondaryApp = SplitScreenUtils.getSecondary(instrumentation)
+
+    @Rule
+    @JvmField
+    val testSetupRule = SplitScreenUtils.testSetupRule({ NavBar.MODE_GESTURAL }, { rotation })
+
+    @Before
+    fun setup() {
+        tapl.setEnableRotation(true)
+        tapl.setExpectedRotation(rotation.value)
+
+        tapl.goHome()
+        primaryApp.launchViaIntent(wmHelper)
+    }
+
+    @Test
+    open fun enterSplitScreenByDragFromAllApps() {
+        tapl.launchedAppState.taskbar
+            .openAllApps()
+            .getAppIcon(secondaryApp.appName)
+            .dragToSplitscreen(secondaryApp.`package`, primaryApp.`package`)
+        SplitScreenUtils.waitForSplitComplete(wmHelper, primaryApp, secondaryApp)
+    }
+
+    @After
+    fun teardown() {
+        primaryApp.exit(wmHelper)
+        secondaryApp.exit(wmHelper)
+    }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromNotification.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromNotification.kt
new file mode 100644
index 0000000..a2b7526
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromNotification.kt
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.service.splitscreen.scenarios
+
+import android.app.Instrumentation
+import android.tools.common.NavBar
+import android.tools.common.Rotation
+import android.tools.device.traces.parsers.WindowManagerStateHelper
+import androidx.test.platform.app.InstrumentationRegistry
+import androidx.test.uiautomator.UiDevice
+import com.android.launcher3.tapl.LauncherInstrumentation
+import org.junit.After
+import org.junit.Before
+import org.junit.Ignore
+import org.junit.Rule
+import org.junit.Test
+
+@Ignore("Base Test Class")
+abstract class EnterSplitScreenByDragFromNotification
+@JvmOverloads
+constructor(val rotation: Rotation = Rotation.ROTATION_0) {
+    private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
+    private val tapl = LauncherInstrumentation()
+    private val wmHelper = WindowManagerStateHelper(instrumentation)
+    private val device = UiDevice.getInstance(instrumentation)
+    private val primaryApp = SplitScreenUtils.getPrimary(instrumentation)
+    private val secondaryApp = SplitScreenUtils.getSecondary(instrumentation)
+    private val sendNotificationApp = SplitScreenUtils.getSendNotification(instrumentation)
+
+    @Rule
+    @JvmField
+    val testSetupRule = SplitScreenUtils.testSetupRule({ NavBar.MODE_GESTURAL }, { rotation })
+
+    @Before
+    fun setup() {
+        tapl.setEnableRotation(true)
+        tapl.setExpectedRotation(rotation.value)
+
+        // Send a notification
+        sendNotificationApp.launchViaIntent(wmHelper)
+        sendNotificationApp.postNotification(wmHelper)
+        tapl.goHome()
+        primaryApp.launchViaIntent(wmHelper)
+    }
+
+    @Test
+    open fun enterSplitScreenByDragFromNotification() {
+        SplitScreenUtils.dragFromNotificationToSplit(instrumentation, device, wmHelper)
+        SplitScreenUtils.waitForSplitComplete(wmHelper, primaryApp, sendNotificationApp)
+    }
+
+    @After
+    fun teardown() {
+        primaryApp.exit(wmHelper)
+        secondaryApp.exit(wmHelper)
+        sendNotificationApp.exit(wmHelper)
+    }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromShortcut.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromShortcut.kt
new file mode 100644
index 0000000..1ccd813
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromShortcut.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.wm.shell.flicker.service.splitscreen.scenarios
+
+import android.app.Instrumentation
+import android.tools.common.NavBar
+import android.tools.common.Rotation
+import android.tools.device.traces.parsers.WindowManagerStateHelper
+import androidx.test.platform.app.InstrumentationRegistry
+import androidx.test.uiautomator.UiDevice
+import com.android.launcher3.tapl.LauncherInstrumentation
+import org.junit.After
+import org.junit.Assume
+import org.junit.Before
+import org.junit.Ignore
+import org.junit.Rule
+import org.junit.Test
+
+@Ignore("Base Test Class")
+abstract class EnterSplitScreenByDragFromShortcut
+@JvmOverloads
+constructor(val rotation: Rotation = Rotation.ROTATION_0) {
+    private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
+    private val tapl = LauncherInstrumentation()
+    private val wmHelper = WindowManagerStateHelper(instrumentation)
+    private val device = UiDevice.getInstance(instrumentation)
+    private val primaryApp = SplitScreenUtils.getPrimary(instrumentation)
+    private val secondaryApp = SplitScreenUtils.getSecondary(instrumentation)
+
+    @Rule
+    @JvmField
+    val testSetupRule = SplitScreenUtils.testSetupRule({ NavBar.MODE_GESTURAL }, { rotation })
+
+    @Before
+    fun setup() {
+        Assume.assumeTrue(tapl.isTablet)
+
+        tapl.setEnableRotation(true)
+        tapl.setExpectedRotation(rotation.value)
+
+        tapl.goHome()
+        SplitScreenUtils.createShortcutOnHotseatIfNotExist(tapl, secondaryApp.appName)
+        primaryApp.launchViaIntent(wmHelper)
+    }
+
+    @Test
+    open fun enterSplitScreenByDragFromShortcut() {
+        tapl.launchedAppState.taskbar
+            .getAppIcon(secondaryApp.appName)
+            .openDeepShortcutMenu()
+            .getMenuItem("Split Screen Secondary Activity")
+            .dragToSplitscreen(secondaryApp.`package`, primaryApp.`package`)
+        SplitScreenUtils.waitForSplitComplete(wmHelper, primaryApp, secondaryApp)
+
+        // TODO: Do we want this check in here? Add to the other tests?
+        //        flicker.splitScreenEntered(
+        //                primaryApp,
+        //                secondaryApp,
+        //                fromOtherApp = false,
+        //                appExistAtStart = false
+        //        )
+    }
+
+    @After
+    fun teardwon() {
+        primaryApp.exit(wmHelper)
+        secondaryApp.exit(wmHelper)
+    }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromTaskbar.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromTaskbar.kt
new file mode 100644
index 0000000..664786b
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromTaskbar.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.wm.shell.flicker.service.splitscreen.scenarios
+
+import android.app.Instrumentation
+import android.tools.common.NavBar
+import android.tools.common.Rotation
+import android.tools.device.traces.parsers.WindowManagerStateHelper
+import androidx.test.platform.app.InstrumentationRegistry
+import androidx.test.uiautomator.UiDevice
+import com.android.launcher3.tapl.LauncherInstrumentation
+import org.junit.After
+import org.junit.Before
+import org.junit.Ignore
+import org.junit.Rule
+import org.junit.Test
+
+@Ignore("Base Test Class")
+abstract class EnterSplitScreenByDragFromTaskbar
+@JvmOverloads
+constructor(val rotation: Rotation = Rotation.ROTATION_0) {
+    private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
+    private val tapl = LauncherInstrumentation()
+    private val wmHelper = WindowManagerStateHelper(instrumentation)
+    private val device = UiDevice.getInstance(instrumentation)
+    private val primaryApp = SplitScreenUtils.getPrimary(instrumentation)
+    private val secondaryApp = SplitScreenUtils.getSecondary(instrumentation)
+
+    @Rule
+    @JvmField
+    val testSetupRule = SplitScreenUtils.testSetupRule({ NavBar.MODE_GESTURAL }, { rotation })
+
+    @Before
+    fun setup() {
+        tapl.setEnableRotation(true)
+        tapl.setExpectedRotation(rotation.value)
+
+        tapl.goHome()
+        SplitScreenUtils.createShortcutOnHotseatIfNotExist(tapl, secondaryApp.appName)
+        primaryApp.launchViaIntent(wmHelper)
+    }
+
+    @Test
+    open fun enterSplitScreenByDragFromTaskbar() {
+        tapl.launchedAppState.taskbar
+            .getAppIcon(secondaryApp.appName)
+            .dragToSplitscreen(secondaryApp.`package`, primaryApp.`package`)
+        SplitScreenUtils.waitForSplitComplete(wmHelper, primaryApp, secondaryApp)
+    }
+
+    @After
+    fun teardown() {
+        primaryApp.exit(wmHelper)
+        secondaryApp.exit(wmHelper)
+    }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenFromOverview.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenFromOverview.kt
new file mode 100644
index 0000000..88fd084
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenFromOverview.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.wm.shell.flicker.service.splitscreen.scenarios
+
+import android.app.Instrumentation
+import android.tools.common.NavBar
+import android.tools.common.Rotation
+import android.tools.device.traces.parsers.WindowManagerStateHelper
+import androidx.test.platform.app.InstrumentationRegistry
+import androidx.test.uiautomator.UiDevice
+import com.android.launcher3.tapl.LauncherInstrumentation
+import org.junit.After
+import org.junit.Before
+import org.junit.Ignore
+import org.junit.Rule
+import org.junit.Test
+
+@Ignore("Base Test Class")
+abstract class EnterSplitScreenFromOverview
+@JvmOverloads
+constructor(val rotation: Rotation = Rotation.ROTATION_0) {
+    private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
+    private val tapl = LauncherInstrumentation()
+    private val wmHelper = WindowManagerStateHelper(instrumentation)
+    private val device = UiDevice.getInstance(instrumentation)
+    private val primaryApp = SplitScreenUtils.getPrimary(instrumentation)
+    private val secondaryApp = SplitScreenUtils.getSecondary(instrumentation)
+
+    @Rule
+    @JvmField
+    val testSetupRule = SplitScreenUtils.testSetupRule({ NavBar.MODE_GESTURAL }, { rotation })
+
+    @Before
+    fun setup() {
+        tapl.setEnableRotation(true)
+        tapl.setExpectedRotation(rotation.value)
+
+        primaryApp.launchViaIntent(wmHelper)
+        secondaryApp.launchViaIntent(wmHelper)
+        tapl.goHome()
+        wmHelper
+            .StateSyncBuilder()
+            .withAppTransitionIdle()
+            .withHomeActivityVisible()
+            .waitForAndVerify()
+    }
+
+    @Test
+    open fun enterSplitScreenFromOverview() {
+        SplitScreenUtils.splitFromOverview(tapl, device)
+        SplitScreenUtils.waitForSplitComplete(wmHelper, primaryApp, secondaryApp)
+    }
+
+    @After
+    fun teardown() {
+        primaryApp.exit(wmHelper)
+        secondaryApp.exit(wmHelper)
+    }
+}
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/service/splitscreen/scenarios/SplitScreenUtils.kt
similarity index 86%
copy from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SplitScreenUtils.kt
copy to libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SplitScreenUtils.kt
index f4828f1..83a18e8 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/service/splitscreen/scenarios/SplitScreenUtils.kt
@@ -14,20 +14,30 @@
  * limitations under the License.
  */
 
-package com.android.wm.shell.flicker.splitscreen
+package com.android.wm.shell.flicker.service.splitscreen.scenarios
 
 import android.app.Instrumentation
 import android.graphics.Point
 import android.os.SystemClock
+import android.platform.test.rule.NavigationModeRule
+import android.platform.test.rule.PressHomeRule
+import android.platform.test.rule.UnlockScreenRule
+import android.tools.common.NavBar
+import android.tools.common.Rotation
 import android.tools.common.traces.component.ComponentNameMatcher
 import android.tools.common.traces.component.IComponentMatcher
 import android.tools.common.traces.component.IComponentNameMatcher
+import android.tools.device.apphelpers.MessagingAppHelper
 import android.tools.device.apphelpers.StandardAppHelper
+import android.tools.device.flicker.rules.ChangeDisplayOrientationRule
+import android.tools.device.flicker.rules.LaunchAppRule
+import android.tools.device.flicker.rules.RemoveAllTasksButHomeRule
 import android.tools.device.traces.parsers.WindowManagerStateHelper
 import android.tools.device.traces.parsers.toFlickerComponent
 import android.view.InputDevice
 import android.view.MotionEvent
 import android.view.ViewConfiguration
+import androidx.test.platform.app.InstrumentationRegistry
 import androidx.test.uiautomator.By
 import androidx.test.uiautomator.BySelector
 import androidx.test.uiautomator.UiDevice
@@ -39,12 +49,12 @@
 import com.android.server.wm.flicker.helpers.NotificationAppHelper
 import com.android.server.wm.flicker.helpers.SimpleAppHelper
 import com.android.server.wm.flicker.testapp.ActivityOptions
-import com.android.server.wm.flicker.testapp.ActivityOptions.SplitScreen.Primary
 import com.android.wm.shell.flicker.LAUNCHER_UI_PACKAGE_NAME
 import com.android.wm.shell.flicker.SYSTEM_UI_PACKAGE_NAME
 import org.junit.Assert.assertNotNull
+import org.junit.rules.RuleChain
 
-internal object SplitScreenUtils {
+object SplitScreenUtils {
     private const val TIMEOUT_MS = 3_000L
     private const val DRAG_DURATION_MS = 1_000L
     private const val NOTIFICATION_SCROLLER = "notification_stack_scroller"
@@ -63,6 +73,30 @@
     private val overviewSnapshotSelector: BySelector
         get() = By.res(LAUNCHER_UI_PACKAGE_NAME, OVERVIEW_SNAPSHOT)
 
+    private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
+
+    fun testSetupRule(navigationMode: () -> NavBar, rotation: () -> Rotation): RuleChain {
+        return RuleChain.outerRule(UnlockScreenRule())
+            .around(
+                NavigationModeRule(
+                    navigationMode().value,
+                    /* changeNavigationModeAfterTest */ false
+                )
+            )
+            .around(
+                LaunchAppRule(MessagingAppHelper(instrumentation), clearCacheAfterParsing = false)
+            )
+            .around(RemoveAllTasksButHomeRule())
+            .around(
+                ChangeDisplayOrientationRule(
+                    rotation(),
+                    resetOrientationAfterTest = false,
+                    clearCacheAfterParsing = false
+                )
+            )
+            .around(PressHomeRule())
+    }
+
     fun getPrimary(instrumentation: Instrumentation): StandardAppHelper =
         SimpleAppHelper(
             instrumentation,
@@ -113,17 +147,6 @@
         waitForSplitComplete(wmHelper, primaryApp, secondaryApp)
     }
 
-    fun enterSplitViaIntent(
-            wmHelper: WindowManagerStateHelper,
-            primaryApp: StandardAppHelper,
-            secondaryApp: StandardAppHelper
-    ) {
-        val stringExtras = mapOf(Primary.EXTRA_LAUNCH_ADJACENT to "true")
-        primaryApp.launchViaIntent(wmHelper, null, null,
-                stringExtras)
-        waitForSplitComplete(wmHelper, primaryApp, secondaryApp)
-    }
-
     fun splitFromOverview(tapl: LauncherInstrumentation, device: UiDevice) {
         // Note: The initial split position in landscape is different between tablet and phone.
         // In landscape, tablet will let the first app split to right side, and phone will
@@ -159,6 +182,17 @@
         SystemClock.sleep(TIMEOUT_MS)
     }
 
+    fun enterSplitViaIntent(
+        wmHelper: WindowManagerStateHelper,
+        primaryApp: StandardAppHelper,
+        secondaryApp: StandardAppHelper
+    ) {
+        val stringExtras =
+            mapOf(ActivityOptions.SplitScreen.Primary.EXTRA_LAUNCH_ADJACENT to "true")
+        primaryApp.launchViaIntent(wmHelper, null, null, stringExtras)
+        SplitScreenUtils.waitForSplitComplete(wmHelper, primaryApp, secondaryApp)
+    }
+
     fun dragFromNotificationToSplit(
         instrumentation: Instrumentation,
         device: UiDevice,
@@ -326,14 +360,14 @@
         dividerBar.drag(
             Point(
                 if (dragToRight) {
-                    displayBounds.right
+                    displayBounds.width * 4 / 5
                 } else {
-                    displayBounds.left
+                    displayBounds.width * 1 / 5
                 },
                 if (dragToBottom) {
-                    displayBounds.bottom
+                    displayBounds.height * 4 / 5
                 } else {
-                    displayBounds.top
+                    displayBounds.height * 1 / 5
                 }
             )
         )
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchAppByDoubleTapDivider.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchAppByDoubleTapDivider.kt
new file mode 100644
index 0000000..e5501f4
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchAppByDoubleTapDivider.kt
@@ -0,0 +1,151 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.service.splitscreen.scenarios
+
+import android.app.Instrumentation
+import android.graphics.Point
+import android.tools.common.NavBar
+import android.tools.common.Rotation
+import android.tools.device.helpers.WindowUtils
+import android.tools.device.traces.parsers.WindowManagerStateHelper
+import androidx.test.platform.app.InstrumentationRegistry
+import androidx.test.uiautomator.UiDevice
+import com.android.launcher3.tapl.LauncherInstrumentation
+import org.junit.After
+import org.junit.Before
+import org.junit.Ignore
+import org.junit.Rule
+import org.junit.Test
+
+@Ignore("Base Test Class")
+abstract class SwitchAppByDoubleTapDivider
+@JvmOverloads
+constructor(val rotation: Rotation = Rotation.ROTATION_0) {
+    private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
+    private val tapl = LauncherInstrumentation()
+    private val wmHelper = WindowManagerStateHelper(instrumentation)
+    private val device = UiDevice.getInstance(instrumentation)
+    private val primaryApp = SplitScreenUtils.getPrimary(instrumentation)
+    private val secondaryApp = SplitScreenUtils.getSecondary(instrumentation)
+
+    @Rule
+    @JvmField
+    val testSetupRule = SplitScreenUtils.testSetupRule({ NavBar.MODE_GESTURAL }, { rotation })
+
+    @Before
+    fun setup() {
+        tapl.setEnableRotation(true)
+        tapl.setExpectedRotation(rotation.value)
+        tapl.workspace.switchToOverview().dismissAllTasks()
+
+        SplitScreenUtils.enterSplit(wmHelper, tapl, device, primaryApp, secondaryApp)
+    }
+
+    @Test
+    open fun switchAppByDoubleTapDivider() {
+        SplitScreenUtils.doubleTapDividerToSwitch(device)
+        wmHelper.StateSyncBuilder().withAppTransitionIdle().waitForAndVerify()
+
+        waitForLayersToSwitch(wmHelper)
+        waitForWindowsToSwitch(wmHelper)
+    }
+
+    @After
+    fun teardown() {
+        primaryApp.exit(wmHelper)
+        secondaryApp.exit(wmHelper)
+    }
+
+    private fun waitForWindowsToSwitch(wmHelper: WindowManagerStateHelper) {
+        wmHelper
+            .StateSyncBuilder()
+            .add("appWindowsSwitched") {
+                val primaryAppWindow =
+                    it.wmState.visibleWindows.firstOrNull { window ->
+                        primaryApp.windowMatchesAnyOf(window)
+                    }
+                        ?: return@add false
+                val secondaryAppWindow =
+                    it.wmState.visibleWindows.firstOrNull { window ->
+                        secondaryApp.windowMatchesAnyOf(window)
+                    }
+                        ?: return@add false
+
+                if (isLandscape(rotation)) {
+                    return@add if (isTablet()) {
+                        secondaryAppWindow.frame.right <= primaryAppWindow.frame.left
+                    } else {
+                        primaryAppWindow.frame.right <= secondaryAppWindow.frame.left
+                    }
+                } else {
+                    return@add if (isTablet()) {
+                        primaryAppWindow.frame.bottom <= secondaryAppWindow.frame.top
+                    } else {
+                        primaryAppWindow.frame.bottom <= secondaryAppWindow.frame.top
+                    }
+                }
+            }
+            .waitForAndVerify()
+    }
+
+    private fun waitForLayersToSwitch(wmHelper: WindowManagerStateHelper) {
+        wmHelper
+            .StateSyncBuilder()
+            .add("appLayersSwitched") {
+                val primaryAppLayer =
+                    it.layerState.visibleLayers.firstOrNull { window ->
+                        primaryApp.layerMatchesAnyOf(window)
+                    }
+                        ?: return@add false
+                val secondaryAppLayer =
+                    it.layerState.visibleLayers.firstOrNull { window ->
+                        secondaryApp.layerMatchesAnyOf(window)
+                    }
+                        ?: return@add false
+
+                val primaryVisibleRegion = primaryAppLayer.visibleRegion?.bounds ?: return@add false
+                val secondaryVisibleRegion =
+                    secondaryAppLayer.visibleRegion?.bounds ?: return@add false
+
+                if (isLandscape(rotation)) {
+                    return@add if (isTablet()) {
+                        secondaryVisibleRegion.right <= primaryVisibleRegion.left
+                    } else {
+                        primaryVisibleRegion.right <= secondaryVisibleRegion.left
+                    }
+                } else {
+                    return@add if (isTablet()) {
+                        primaryVisibleRegion.bottom <= secondaryVisibleRegion.top
+                    } else {
+                        primaryVisibleRegion.bottom <= secondaryVisibleRegion.top
+                    }
+                }
+            }
+            .waitForAndVerify()
+    }
+
+    private fun isLandscape(rotation: Rotation): Boolean {
+        val displayBounds = WindowUtils.getDisplayBounds(rotation)
+        return displayBounds.width > displayBounds.height
+    }
+
+    private fun isTablet(): Boolean {
+        val sizeDp: Point = device.displaySizeDp
+        val LARGE_SCREEN_DP_THRESHOLD = 600
+        return sizeDp.x >= LARGE_SCREEN_DP_THRESHOLD && sizeDp.y >= LARGE_SCREEN_DP_THRESHOLD
+    }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBackToSplitFromAnotherApp.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBackToSplitFromAnotherApp.kt
new file mode 100644
index 0000000..b3f1e87
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBackToSplitFromAnotherApp.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.wm.shell.flicker.service.splitscreen.scenarios
+
+import android.app.Instrumentation
+import android.tools.common.NavBar
+import android.tools.common.Rotation
+import android.tools.device.traces.parsers.WindowManagerStateHelper
+import androidx.test.platform.app.InstrumentationRegistry
+import androidx.test.uiautomator.UiDevice
+import com.android.launcher3.tapl.LauncherInstrumentation
+import org.junit.After
+import org.junit.Before
+import org.junit.Ignore
+import org.junit.Rule
+import org.junit.Test
+
+@Ignore("Base Test Class")
+abstract class SwitchBackToSplitFromAnotherApp
+@JvmOverloads
+constructor(val rotation: Rotation = Rotation.ROTATION_0) {
+    private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
+    private val tapl = LauncherInstrumentation()
+    private val wmHelper = WindowManagerStateHelper(instrumentation)
+    private val device = UiDevice.getInstance(instrumentation)
+    private val primaryApp = SplitScreenUtils.getPrimary(instrumentation)
+    private val secondaryApp = SplitScreenUtils.getSecondary(instrumentation)
+    private val thirdApp = SplitScreenUtils.getNonResizeable(instrumentation)
+
+    @Rule
+    @JvmField
+    val testSetupRule = SplitScreenUtils.testSetupRule({ NavBar.MODE_GESTURAL }, { rotation })
+
+    @Before
+    fun setup() {
+        tapl.setEnableRotation(true)
+        tapl.setExpectedRotation(rotation.value)
+
+        SplitScreenUtils.enterSplit(wmHelper, tapl, device, primaryApp, secondaryApp)
+
+        thirdApp.launchViaIntent(wmHelper)
+        wmHelper.StateSyncBuilder().withWindowSurfaceAppeared(thirdApp).waitForAndVerify()
+    }
+
+    @Test
+    open fun switchBackToSplitFromAnotherApp() {
+        tapl.launchedAppState.quickSwitchToPreviousApp()
+        SplitScreenUtils.waitForSplitComplete(wmHelper, primaryApp, secondaryApp)
+    }
+
+    @After
+    fun teardown() {
+        primaryApp.exit(wmHelper)
+        secondaryApp.exit(wmHelper)
+    }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBackToSplitFromHome.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBackToSplitFromHome.kt
new file mode 100644
index 0000000..d112116
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBackToSplitFromHome.kt
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.service.splitscreen.scenarios
+
+import android.app.Instrumentation
+import android.tools.common.NavBar
+import android.tools.common.Rotation
+import android.tools.device.traces.parsers.WindowManagerStateHelper
+import androidx.test.platform.app.InstrumentationRegistry
+import androidx.test.uiautomator.UiDevice
+import com.android.launcher3.tapl.LauncherInstrumentation
+import org.junit.After
+import org.junit.Before
+import org.junit.Ignore
+import org.junit.Rule
+import org.junit.Test
+
+@Ignore("Base Test Class")
+abstract class SwitchBackToSplitFromHome
+@JvmOverloads
+constructor(val rotation: Rotation = Rotation.ROTATION_0) {
+    private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
+    private val tapl = LauncherInstrumentation()
+    private val wmHelper = WindowManagerStateHelper(instrumentation)
+    private val device = UiDevice.getInstance(instrumentation)
+    private val primaryApp = SplitScreenUtils.getPrimary(instrumentation)
+    private val secondaryApp = SplitScreenUtils.getSecondary(instrumentation)
+
+    @Rule
+    @JvmField
+    val testSetupRule = SplitScreenUtils.testSetupRule({ NavBar.MODE_GESTURAL }, { rotation })
+
+    @Before
+    fun setup() {
+        tapl.setEnableRotation(true)
+        tapl.setExpectedRotation(rotation.value)
+
+        SplitScreenUtils.enterSplit(wmHelper, tapl, device, primaryApp, secondaryApp)
+
+        tapl.goHome()
+        wmHelper.StateSyncBuilder().withHomeActivityVisible().waitForAndVerify()
+    }
+
+    @Test
+    open fun switchBackToSplitFromHome() {
+        tapl.workspace.quickSwitchToPreviousApp()
+        SplitScreenUtils.waitForSplitComplete(wmHelper, primaryApp, secondaryApp)
+    }
+
+    @After
+    fun teardown() {
+        primaryApp.exit(wmHelper)
+        secondaryApp.exit(wmHelper)
+    }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBackToSplitFromRecent.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBackToSplitFromRecent.kt
new file mode 100644
index 0000000..9ab924c
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBackToSplitFromRecent.kt
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.service.splitscreen.scenarios
+
+import android.app.Instrumentation
+import android.tools.common.NavBar
+import android.tools.common.Rotation
+import android.tools.device.traces.parsers.WindowManagerStateHelper
+import androidx.test.platform.app.InstrumentationRegistry
+import androidx.test.uiautomator.UiDevice
+import com.android.launcher3.tapl.LauncherInstrumentation
+import org.junit.After
+import org.junit.Before
+import org.junit.Ignore
+import org.junit.Rule
+import org.junit.Test
+
+@Ignore("Base Test Class")
+abstract class SwitchBackToSplitFromRecent
+@JvmOverloads
+constructor(val rotation: Rotation = Rotation.ROTATION_0) {
+    private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
+    private val tapl = LauncherInstrumentation()
+    private val wmHelper = WindowManagerStateHelper(instrumentation)
+    private val device = UiDevice.getInstance(instrumentation)
+    private val primaryApp = SplitScreenUtils.getPrimary(instrumentation)
+    private val secondaryApp = SplitScreenUtils.getSecondary(instrumentation)
+
+    @Rule
+    @JvmField
+    val testSetupRule = SplitScreenUtils.testSetupRule({ NavBar.MODE_GESTURAL }, { rotation })
+
+    @Before
+    fun setup() {
+        tapl.setEnableRotation(true)
+        tapl.setExpectedRotation(rotation.value)
+
+        SplitScreenUtils.enterSplit(wmHelper, tapl, device, primaryApp, secondaryApp)
+
+        tapl.goHome()
+        wmHelper.StateSyncBuilder().withHomeActivityVisible().waitForAndVerify()
+    }
+
+    @Test
+    open fun switchBackToSplitFromRecent() {
+        tapl.workspace.switchToOverview().currentTask.open()
+        SplitScreenUtils.waitForSplitComplete(wmHelper, primaryApp, secondaryApp)
+    }
+
+    @After
+    fun teardown() {
+        primaryApp.exit(wmHelper)
+        secondaryApp.exit(wmHelper)
+    }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBetweenSplitPairs.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBetweenSplitPairs.kt
new file mode 100644
index 0000000..b694dfa
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBetweenSplitPairs.kt
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.service.splitscreen.scenarios
+
+import android.app.Instrumentation
+import android.tools.common.NavBar
+import android.tools.common.Rotation
+import android.tools.device.traces.parsers.WindowManagerStateHelper
+import androidx.test.platform.app.InstrumentationRegistry
+import androidx.test.uiautomator.UiDevice
+import com.android.launcher3.tapl.LauncherInstrumentation
+import org.junit.After
+import org.junit.Before
+import org.junit.Ignore
+import org.junit.Rule
+import org.junit.Test
+
+@Ignore("Base Test Class")
+abstract class SwitchBetweenSplitPairs
+@JvmOverloads
+constructor(val rotation: Rotation = Rotation.ROTATION_0) {
+    private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
+    private val tapl = LauncherInstrumentation()
+    private val wmHelper = WindowManagerStateHelper(instrumentation)
+    private val device = UiDevice.getInstance(instrumentation)
+    private val primaryApp = SplitScreenUtils.getPrimary(instrumentation)
+    private val secondaryApp = SplitScreenUtils.getSecondary(instrumentation)
+    private val thirdApp = SplitScreenUtils.getIme(instrumentation)
+    private val fourthApp = SplitScreenUtils.getSendNotification(instrumentation)
+
+    @Rule
+    @JvmField
+    val testSetupRule = SplitScreenUtils.testSetupRule({ NavBar.MODE_GESTURAL }, { rotation })
+
+    @Before
+    fun setup() {
+        tapl.setEnableRotation(true)
+        tapl.setExpectedRotation(rotation.value)
+
+        SplitScreenUtils.enterSplit(wmHelper, tapl, device, primaryApp, secondaryApp)
+        SplitScreenUtils.enterSplit(wmHelper, tapl, device, thirdApp, fourthApp)
+        SplitScreenUtils.waitForSplitComplete(wmHelper, thirdApp, fourthApp)
+    }
+
+    @Test
+    open fun switchBetweenSplitPairs() {
+        tapl.launchedAppState.quickSwitchToPreviousApp()
+        SplitScreenUtils.waitForSplitComplete(wmHelper, primaryApp, secondaryApp)
+    }
+
+    @After
+    fun teardown() {
+        primaryApp.exit(wmHelper)
+        secondaryApp.exit(wmHelper)
+        thirdApp.exit(wmHelper)
+        fourthApp.exit(wmHelper)
+    }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/UnlockKeyguardToSplitScreen.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/UnlockKeyguardToSplitScreen.kt
new file mode 100644
index 0000000..f78b788
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/UnlockKeyguardToSplitScreen.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.wm.shell.flicker.service.splitscreen.scenarios
+
+import android.app.Instrumentation
+import android.tools.common.NavBar
+import android.tools.common.Rotation
+import android.tools.device.traces.parsers.WindowManagerStateHelper
+import androidx.test.platform.app.InstrumentationRegistry
+import androidx.test.uiautomator.UiDevice
+import com.android.launcher3.tapl.LauncherInstrumentation
+import org.junit.After
+import org.junit.Before
+import org.junit.Ignore
+import org.junit.Rule
+import org.junit.Test
+
+@Ignore("Base Test Class")
+abstract class UnlockKeyguardToSplitScreen {
+    private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
+    private val tapl = LauncherInstrumentation()
+    private val wmHelper = WindowManagerStateHelper(instrumentation)
+    private val device = UiDevice.getInstance(instrumentation)
+    private val primaryApp = SplitScreenUtils.getPrimary(instrumentation)
+    private val secondaryApp = SplitScreenUtils.getSecondary(instrumentation)
+
+    @Rule
+    @JvmField
+    val testSetupRule =
+        SplitScreenUtils.testSetupRule({ NavBar.MODE_GESTURAL }, { Rotation.ROTATION_0 })
+
+    @Before
+    fun setup() {
+        tapl.setEnableRotation(true)
+        tapl.setExpectedRotation(Rotation.ROTATION_0.value)
+
+        SplitScreenUtils.enterSplitViaIntent(wmHelper, primaryApp, secondaryApp)
+    }
+
+    @Test
+    open fun unlockKeyguardToSplitScreen() {
+        device.sleep()
+        wmHelper.StateSyncBuilder().withAppTransitionIdle().waitForAndVerify()
+        device.wakeUp()
+        device.pressMenu()
+        wmHelper.StateSyncBuilder().withAppTransitionIdle().waitForAndVerify()
+    }
+
+    @After
+    fun teardown() {
+        primaryApp.exit(wmHelper)
+        secondaryApp.exit(wmHelper)
+    }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/CopyContentInSplit.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/CopyContentInSplit.kt
index a43ad9b..1d4c4d2 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/CopyContentInSplit.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/CopyContentInSplit.kt
@@ -17,7 +17,6 @@
 package com.android.wm.shell.flicker.splitscreen
 
 import android.platform.test.annotations.FlakyTest
-import android.platform.test.annotations.PlatinumTest
 import android.platform.test.annotations.Presubmit
 import android.tools.common.traces.component.ComponentNameMatcher
 import android.tools.common.traces.component.EdgeExtensionComponentMatcher
@@ -28,13 +27,9 @@
 import androidx.test.filters.RequiresDevice
 import com.android.wm.shell.flicker.ICommonAssertions
 import com.android.wm.shell.flicker.SPLIT_SCREEN_DIVIDER_COMPONENT
-import com.android.wm.shell.flicker.appWindowIsVisibleAtEnd
-import com.android.wm.shell.flicker.appWindowIsVisibleAtStart
 import com.android.wm.shell.flicker.appWindowKeepVisible
 import com.android.wm.shell.flicker.layerKeepVisible
 import com.android.wm.shell.flicker.splitAppLayerBoundsKeepVisible
-import com.android.wm.shell.flicker.splitScreenDividerIsVisibleAtEnd
-import com.android.wm.shell.flicker.splitScreenDividerIsVisibleAtStart
 import com.android.wm.shell.flicker.splitscreen.benchmark.CopyContentInSplitBenchmark
 import org.junit.FixMethodOrder
 import org.junit.Test
@@ -60,21 +55,6 @@
             thisTransition(this)
         }
 
-    @PlatinumTest(focusArea = "sysui")
-    @Presubmit
-    @Test
-    override fun cujCompleted() {
-        flicker.appWindowIsVisibleAtStart(primaryApp)
-        flicker.appWindowIsVisibleAtStart(textEditApp)
-        flicker.splitScreenDividerIsVisibleAtStart()
-
-        flicker.appWindowIsVisibleAtEnd(primaryApp)
-        flicker.appWindowIsVisibleAtEnd(textEditApp)
-        flicker.splitScreenDividerIsVisibleAtEnd()
-
-        // The validation of copied text is already done in SplitScreenUtils.copyContentInSplit()
-    }
-
     @Presubmit
     @Test
     fun splitScreenDividerKeepVisible() = flicker.layerKeepVisible(SPLIT_SCREEN_DIVIDER_COMPONENT)
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DragDividerToResize.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DragDividerToResize.kt
index a118c08..0d967eb 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DragDividerToResize.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DragDividerToResize.kt
@@ -17,7 +17,6 @@
 package com.android.wm.shell.flicker.splitscreen
 
 import android.platform.test.annotations.FlakyTest
-import android.platform.test.annotations.PlatinumTest
 import android.platform.test.annotations.Presubmit
 import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
 import android.tools.device.flicker.legacy.FlickerBuilder
@@ -26,13 +25,9 @@
 import androidx.test.filters.RequiresDevice
 import com.android.wm.shell.flicker.ICommonAssertions
 import com.android.wm.shell.flicker.SPLIT_SCREEN_DIVIDER_COMPONENT
-import com.android.wm.shell.flicker.appWindowIsVisibleAtEnd
-import com.android.wm.shell.flicker.appWindowIsVisibleAtStart
 import com.android.wm.shell.flicker.appWindowKeepVisible
 import com.android.wm.shell.flicker.layerKeepVisible
 import com.android.wm.shell.flicker.splitAppLayerBoundsChanges
-import com.android.wm.shell.flicker.splitScreenDividerIsVisibleAtEnd
-import com.android.wm.shell.flicker.splitScreenDividerIsVisibleAtStart
 import com.android.wm.shell.flicker.splitscreen.benchmark.DragDividerToResizeBenchmark
 import org.junit.FixMethodOrder
 import org.junit.Test
@@ -58,19 +53,6 @@
             thisTransition(this)
         }
 
-    @PlatinumTest(focusArea = "sysui")
-    @Presubmit
-    @Test
-    override fun cujCompleted() {
-        flicker.appWindowIsVisibleAtStart(primaryApp)
-        flicker.appWindowIsVisibleAtStart(secondaryApp)
-        flicker.splitScreenDividerIsVisibleAtStart()
-
-        flicker.appWindowIsVisibleAtEnd(primaryApp)
-        flicker.appWindowIsVisibleAtEnd(secondaryApp)
-        flicker.splitScreenDividerIsVisibleAtEnd()
-    }
-
     @Presubmit
     @Test
     fun splitScreenDividerKeepVisible() = flicker.layerKeepVisible(SPLIT_SCREEN_DIVIDER_COMPONENT)
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SplitScreenBase.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SplitScreenBase.kt
index 580b153..d3434a5 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SplitScreenBase.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SplitScreenBase.kt
@@ -21,6 +21,7 @@
 import android.tools.device.flicker.legacy.LegacyFlickerTest
 import com.android.server.wm.flicker.helpers.setRotation
 import com.android.wm.shell.flicker.BaseBenchmarkTest
+import com.android.wm.shell.flicker.SplitScreenUtils
 
 abstract class SplitScreenBase(flicker: LegacyFlickerTest) : BaseBenchmarkTest(flicker) {
     protected val context: Context = instrumentation.context
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchAppByDoubleTapDivider.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchAppByDoubleTapDivider.kt
index e0a47b3..f236c2d 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchAppByDoubleTapDivider.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchAppByDoubleTapDivider.kt
@@ -16,7 +16,6 @@
 
 package com.android.wm.shell.flicker.splitscreen
 
-import android.platform.test.annotations.PlatinumTest
 import android.platform.test.annotations.Postsubmit
 import android.platform.test.annotations.Presubmit
 import android.tools.common.NavBar
@@ -28,12 +27,9 @@
 import com.android.wm.shell.flicker.ICommonAssertions
 import com.android.wm.shell.flicker.SPLIT_SCREEN_DIVIDER_COMPONENT
 import com.android.wm.shell.flicker.appWindowIsVisibleAtEnd
-import com.android.wm.shell.flicker.appWindowIsVisibleAtStart
 import com.android.wm.shell.flicker.layerIsVisibleAtEnd
 import com.android.wm.shell.flicker.layerKeepVisible
 import com.android.wm.shell.flicker.splitAppLayerBoundsIsVisibleAtEnd
-import com.android.wm.shell.flicker.splitScreenDividerIsVisibleAtEnd
-import com.android.wm.shell.flicker.splitScreenDividerIsVisibleAtStart
 import com.android.wm.shell.flicker.splitscreen.benchmark.SwitchAppByDoubleTapDividerBenchmark
 import org.junit.FixMethodOrder
 import org.junit.Test
@@ -59,19 +55,6 @@
             thisTransition(this)
         }
 
-    @PlatinumTest(focusArea = "sysui")
-    @Presubmit
-    @Test
-    override fun cujCompleted() {
-        flicker.appWindowIsVisibleAtStart(primaryApp)
-        flicker.appWindowIsVisibleAtStart(secondaryApp)
-        flicker.splitScreenDividerIsVisibleAtStart()
-
-        flicker.appWindowIsVisibleAtEnd(primaryApp)
-        flicker.appWindowIsVisibleAtEnd(secondaryApp)
-        flicker.splitScreenDividerIsVisibleAtEnd()
-    }
-
     @Presubmit
     @Test
     fun splitScreenDividerKeepVisible() = flicker.layerKeepVisible(SPLIT_SCREEN_DIVIDER_COMPONENT)
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBetweenSplitPairs.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBetweenSplitPairs.kt
index 8f867df..8aaa98a 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBetweenSplitPairs.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBetweenSplitPairs.kt
@@ -17,7 +17,6 @@
 package com.android.wm.shell.flicker.splitscreen
 
 import android.platform.test.annotations.FlakyTest
-import android.platform.test.annotations.PlatinumTest
 import android.platform.test.annotations.Presubmit
 import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
 import android.tools.device.flicker.legacy.FlickerBuilder
@@ -28,15 +27,10 @@
 import com.android.wm.shell.flicker.SPLIT_SCREEN_DIVIDER_COMPONENT
 import com.android.wm.shell.flicker.appWindowBecomesInvisible
 import com.android.wm.shell.flicker.appWindowBecomesVisible
-import com.android.wm.shell.flicker.appWindowIsInvisibleAtEnd
-import com.android.wm.shell.flicker.appWindowIsVisibleAtEnd
-import com.android.wm.shell.flicker.appWindowIsVisibleAtStart
 import com.android.wm.shell.flicker.layerBecomesInvisible
 import com.android.wm.shell.flicker.layerBecomesVisible
 import com.android.wm.shell.flicker.splitAppLayerBoundsIsVisibleAtEnd
 import com.android.wm.shell.flicker.splitAppLayerBoundsSnapToDivider
-import com.android.wm.shell.flicker.splitScreenDividerIsVisibleAtEnd
-import com.android.wm.shell.flicker.splitScreenDividerIsVisibleAtStart
 import com.android.wm.shell.flicker.splitscreen.benchmark.SwitchBetweenSplitPairsBenchmark
 import org.junit.FixMethodOrder
 import org.junit.Test
@@ -62,21 +56,6 @@
             thisTransition(this)
         }
 
-    @PlatinumTest(focusArea = "sysui")
-    @Presubmit
-    @Test
-    override fun cujCompleted() {
-        flicker.appWindowIsVisibleAtStart(thirdApp)
-        flicker.appWindowIsVisibleAtStart(fourthApp)
-        flicker.splitScreenDividerIsVisibleAtStart()
-
-        flicker.appWindowIsVisibleAtEnd(primaryApp)
-        flicker.appWindowIsVisibleAtEnd(secondaryApp)
-        flicker.appWindowIsInvisibleAtEnd(thirdApp)
-        flicker.appWindowIsInvisibleAtEnd(fourthApp)
-        flicker.splitScreenDividerIsVisibleAtEnd()
-    }
-
     @Presubmit
     @Test
     fun splitScreenDividerInvisibleAtMiddle() =
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/CopyContentInSplitBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/CopyContentInSplitBenchmark.kt
index d1ca9ea..d9d22de 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/CopyContentInSplitBenchmark.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/CopyContentInSplitBenchmark.kt
@@ -16,18 +16,15 @@
 
 package com.android.wm.shell.flicker.splitscreen.benchmark
 
-import android.platform.test.annotations.PlatinumTest
-import android.platform.test.annotations.Presubmit
 import android.tools.common.traces.component.ComponentNameMatcher
 import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
 import android.tools.device.flicker.legacy.FlickerBuilder
 import android.tools.device.flicker.legacy.LegacyFlickerTest
 import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
 import androidx.test.filters.RequiresDevice
+import com.android.wm.shell.flicker.SplitScreenUtils
 import com.android.wm.shell.flicker.splitscreen.SplitScreenBase
-import com.android.wm.shell.flicker.splitscreen.SplitScreenUtils
 import org.junit.FixMethodOrder
-import org.junit.Test
 import org.junit.runner.RunWith
 import org.junit.runners.MethodSorters
 import org.junit.runners.Parameterized
@@ -36,7 +33,7 @@
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
-open class CopyContentInSplitBenchmark(override val flicker: LegacyFlickerTest) :
+abstract class CopyContentInSplitBenchmark(override val flicker: LegacyFlickerTest) :
     SplitScreenBase(flicker) {
     protected val textEditApp = SplitScreenUtils.getIme(instrumentation)
     protected val magnifierLayer = ComponentNameMatcher("", "magnifier surface bbq wrapper#")
@@ -54,21 +51,6 @@
             }
         }
 
-    override val transition: FlickerBuilder.() -> Unit
-        get() = {
-            withoutTracing(this)
-            defaultSetup(this)
-            defaultTeardown(this)
-            thisTransition(this)
-        }
-
-    @PlatinumTest(focusArea = "sysui")
-    @Presubmit
-    @Test
-    open fun cujCompleted() {
-        // The validation of copied text is already done in SplitScreenUtils.copyContentInSplit()
-    }
-
     companion object {
         @Parameterized.Parameters(name = "{0}")
         @JvmStatic
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/DismissSplitScreenByDividerBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/DismissSplitScreenByDividerBenchmark.kt
index 73acb1f..7e8d60b4 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/DismissSplitScreenByDividerBenchmark.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/DismissSplitScreenByDividerBenchmark.kt
@@ -16,18 +16,14 @@
 
 package com.android.wm.shell.flicker.splitscreen.benchmark
 
-import android.platform.test.annotations.PlatinumTest
-import android.platform.test.annotations.Presubmit
 import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
 import android.tools.device.flicker.legacy.FlickerBuilder
 import android.tools.device.flicker.legacy.LegacyFlickerTest
 import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
 import androidx.test.filters.RequiresDevice
-import com.android.wm.shell.flicker.splitScreenDismissed
+import com.android.wm.shell.flicker.SplitScreenUtils
 import com.android.wm.shell.flicker.splitscreen.SplitScreenBase
-import com.android.wm.shell.flicker.splitscreen.SplitScreenUtils
 import org.junit.FixMethodOrder
-import org.junit.Test
 import org.junit.runner.RunWith
 import org.junit.runners.MethodSorters
 import org.junit.runners.Parameterized
@@ -36,7 +32,7 @@
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
-open class DismissSplitScreenByDividerBenchmark(override val flicker: LegacyFlickerTest) :
+abstract class DismissSplitScreenByDividerBenchmark(override val flicker: LegacyFlickerTest) :
     SplitScreenBase(flicker) {
     protected val thisTransition: FlickerBuilder.() -> Unit
         get() = {
@@ -61,19 +57,6 @@
             }
         }
 
-    override val transition: FlickerBuilder.() -> Unit
-        get() = {
-            withoutTracing(this)
-            defaultSetup(this)
-            defaultTeardown(this)
-            thisTransition(this)
-        }
-
-    @PlatinumTest(focusArea = "sysui")
-    @Presubmit
-    @Test
-    fun cujCompleted() = flicker.splitScreenDismissed(primaryApp, secondaryApp, toHome = false)
-
     companion object {
         @Parameterized.Parameters(name = "{0}")
         @JvmStatic
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/DismissSplitScreenByGoHomeBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/DismissSplitScreenByGoHomeBenchmark.kt
index 86ffd2a..770e032 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/DismissSplitScreenByGoHomeBenchmark.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/DismissSplitScreenByGoHomeBenchmark.kt
@@ -16,18 +16,14 @@
 
 package com.android.wm.shell.flicker.splitscreen.benchmark
 
-import android.platform.test.annotations.PlatinumTest
-import android.platform.test.annotations.Presubmit
 import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
 import android.tools.device.flicker.legacy.FlickerBuilder
 import android.tools.device.flicker.legacy.LegacyFlickerTest
 import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
 import androidx.test.filters.RequiresDevice
-import com.android.wm.shell.flicker.splitScreenDismissed
+import com.android.wm.shell.flicker.SplitScreenUtils
 import com.android.wm.shell.flicker.splitscreen.SplitScreenBase
-import com.android.wm.shell.flicker.splitscreen.SplitScreenUtils
 import org.junit.FixMethodOrder
-import org.junit.Test
 import org.junit.runner.RunWith
 import org.junit.runners.MethodSorters
 import org.junit.runners.Parameterized
@@ -36,7 +32,7 @@
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
-open class DismissSplitScreenByGoHomeBenchmark(override val flicker: LegacyFlickerTest) :
+abstract class DismissSplitScreenByGoHomeBenchmark(override val flicker: LegacyFlickerTest) :
     SplitScreenBase(flicker) {
     protected val thisTransition: FlickerBuilder.() -> Unit
         get() = {
@@ -47,19 +43,6 @@
             }
         }
 
-    override val transition: FlickerBuilder.() -> Unit
-        get() = {
-            withoutTracing(this)
-            defaultSetup(this)
-            defaultTeardown(this)
-            thisTransition(this)
-        }
-
-    @PlatinumTest(focusArea = "sysui")
-    @Presubmit
-    @Test
-    fun cujCompleted() = flicker.splitScreenDismissed(primaryApp, secondaryApp, toHome = true)
-
     companion object {
         @Parameterized.Parameters(name = "{0}")
         @JvmStatic
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/DragDividerToResizeBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/DragDividerToResizeBenchmark.kt
index dfde3b6..46570fd 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/DragDividerToResizeBenchmark.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/DragDividerToResizeBenchmark.kt
@@ -16,19 +16,16 @@
 
 package com.android.wm.shell.flicker.splitscreen.benchmark
 
-import android.platform.test.annotations.PlatinumTest
-import android.platform.test.annotations.Presubmit
 import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
 import android.tools.device.flicker.legacy.FlickerBuilder
 import android.tools.device.flicker.legacy.LegacyFlickerTest
 import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
 import androidx.test.filters.RequiresDevice
+import com.android.wm.shell.flicker.SplitScreenUtils
 import com.android.wm.shell.flicker.splitscreen.SplitScreenBase
-import com.android.wm.shell.flicker.splitscreen.SplitScreenUtils
 import org.junit.Assume
 import org.junit.Before
 import org.junit.FixMethodOrder
-import org.junit.Test
 import org.junit.runner.RunWith
 import org.junit.runners.MethodSorters
 import org.junit.runners.Parameterized
@@ -37,7 +34,7 @@
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
-open class DragDividerToResizeBenchmark(override val flicker: LegacyFlickerTest) :
+abstract class DragDividerToResizeBenchmark(override val flicker: LegacyFlickerTest) :
     SplitScreenBase(flicker) {
     protected val thisTransition: FlickerBuilder.() -> Unit
         get() = {
@@ -45,27 +42,11 @@
             transitions { SplitScreenUtils.dragDividerToResizeAndWait(device, wmHelper) }
         }
 
-    override val transition: FlickerBuilder.() -> Unit
-        get() = {
-            withoutTracing(this)
-            defaultSetup(this)
-            defaultTeardown(this)
-            thisTransition(this)
-        }
-
     @Before
     fun before() {
         Assume.assumeTrue(tapl.isTablet || !flicker.scenario.isLandscapeOrSeascapeAtStart)
     }
 
-    @PlatinumTest(focusArea = "sysui")
-    @Presubmit
-    @Test
-    open fun cujCompleted() {
-        // TODO(b/246490534): Add validation for resized app after withAppTransitionIdle is
-        // robust enough to get the correct end state.
-    }
-
     companion object {
         @Parameterized.Parameters(name = "{0}")
         @JvmStatic
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromAllAppsBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromAllAppsBenchmark.kt
index d13e413..5c3d4ff 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromAllAppsBenchmark.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromAllAppsBenchmark.kt
@@ -16,21 +16,17 @@
 
 package com.android.wm.shell.flicker.splitscreen.benchmark
 
-import android.platform.test.annotations.PlatinumTest
-import android.platform.test.annotations.Presubmit
 import android.tools.common.NavBar
 import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
 import android.tools.device.flicker.legacy.FlickerBuilder
 import android.tools.device.flicker.legacy.LegacyFlickerTest
 import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
 import androidx.test.filters.RequiresDevice
-import com.android.wm.shell.flicker.splitScreenEntered
+import com.android.wm.shell.flicker.SplitScreenUtils
 import com.android.wm.shell.flicker.splitscreen.SplitScreenBase
-import com.android.wm.shell.flicker.splitscreen.SplitScreenUtils
 import org.junit.Assume
 import org.junit.Before
 import org.junit.FixMethodOrder
-import org.junit.Test
 import org.junit.runner.RunWith
 import org.junit.runners.MethodSorters
 import org.junit.runners.Parameterized
@@ -39,7 +35,7 @@
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
-open class EnterSplitScreenByDragFromAllAppsBenchmark(override val flicker: LegacyFlickerTest) :
+abstract class EnterSplitScreenByDragFromAllAppsBenchmark(override val flicker: LegacyFlickerTest) :
     SplitScreenBase(flicker) {
 
     protected val thisTransition: FlickerBuilder.() -> Unit
@@ -57,30 +53,11 @@
             }
         }
 
-    override val transition: FlickerBuilder.() -> Unit
-        get() = {
-            withoutTracing(this)
-            defaultSetup(this)
-            defaultTeardown(this)
-            thisTransition(this)
-        }
-
     @Before
     fun before() {
         Assume.assumeTrue(tapl.isTablet)
     }
 
-    @PlatinumTest(focusArea = "sysui")
-    @Presubmit
-    @Test
-    fun cujCompleted() =
-        flicker.splitScreenEntered(
-            primaryApp,
-            secondaryApp,
-            fromOtherApp = false,
-            appExistAtStart = false
-        )
-
     companion object {
         @Parameterized.Parameters(name = "{0}")
         @JvmStatic
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromNotificationBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromNotificationBenchmark.kt
index 1d41669..6b122c6 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromNotificationBenchmark.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromNotificationBenchmark.kt
@@ -16,21 +16,17 @@
 
 package com.android.wm.shell.flicker.splitscreen.benchmark
 
-import android.platform.test.annotations.PlatinumTest
-import android.platform.test.annotations.Presubmit
 import android.tools.common.NavBar
 import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
 import android.tools.device.flicker.legacy.FlickerBuilder
 import android.tools.device.flicker.legacy.LegacyFlickerTest
 import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
 import androidx.test.filters.RequiresDevice
-import com.android.wm.shell.flicker.splitScreenEntered
+import com.android.wm.shell.flicker.SplitScreenUtils
 import com.android.wm.shell.flicker.splitscreen.SplitScreenBase
-import com.android.wm.shell.flicker.splitscreen.SplitScreenUtils
 import org.junit.Assume
 import org.junit.Before
 import org.junit.FixMethodOrder
-import org.junit.Test
 import org.junit.runner.RunWith
 import org.junit.runners.MethodSorters
 import org.junit.runners.Parameterized
@@ -39,7 +35,7 @@
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
-open class EnterSplitScreenByDragFromNotificationBenchmark(
+abstract class EnterSplitScreenByDragFromNotificationBenchmark(
     override val flicker: LegacyFlickerTest
 ) : SplitScreenBase(flicker) {
     protected val sendNotificationApp = SplitScreenUtils.getSendNotification(instrumentation)
@@ -59,21 +55,6 @@
             teardown { sendNotificationApp.exit(wmHelper) }
         }
 
-    /** {@inheritDoc} */
-    override val transition: FlickerBuilder.() -> Unit
-        get() = {
-            withoutTracing(this)
-            defaultSetup(this)
-            defaultTeardown(this)
-            thisTransition(this)
-        }
-
-    @PlatinumTest(focusArea = "sysui")
-    @Presubmit
-    @Test
-    fun cujCompleted() =
-        flicker.splitScreenEntered(primaryApp, sendNotificationApp, fromOtherApp = false)
-
     @Before
     fun before() {
         Assume.assumeTrue(tapl.isTablet)
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromShortcutBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromShortcutBenchmark.kt
index b4bafa7..78f9bab 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromShortcutBenchmark.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromShortcutBenchmark.kt
@@ -16,21 +16,17 @@
 
 package com.android.wm.shell.flicker.splitscreen.benchmark
 
-import android.platform.test.annotations.PlatinumTest
-import android.platform.test.annotations.Presubmit
 import android.tools.common.NavBar
 import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
 import android.tools.device.flicker.legacy.FlickerBuilder
 import android.tools.device.flicker.legacy.LegacyFlickerTest
 import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
 import androidx.test.filters.RequiresDevice
-import com.android.wm.shell.flicker.splitScreenEntered
+import com.android.wm.shell.flicker.SplitScreenUtils
 import com.android.wm.shell.flicker.splitscreen.SplitScreenBase
-import com.android.wm.shell.flicker.splitscreen.SplitScreenUtils
 import org.junit.Assume
 import org.junit.Before
 import org.junit.FixMethodOrder
-import org.junit.Test
 import org.junit.runner.RunWith
 import org.junit.runners.MethodSorters
 import org.junit.runners.Parameterized
@@ -39,8 +35,9 @@
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
-open class EnterSplitScreenByDragFromShortcutBenchmark(override val flicker: LegacyFlickerTest) :
-    SplitScreenBase(flicker) {
+abstract class EnterSplitScreenByDragFromShortcutBenchmark(
+    override val flicker: LegacyFlickerTest
+) : SplitScreenBase(flicker) {
     @Before
     fun before() {
         Assume.assumeTrue(tapl.isTablet)
@@ -62,25 +59,6 @@
         }
     }
 
-    override val transition: FlickerBuilder.() -> Unit
-        get() = {
-            withoutTracing(this)
-            defaultSetup(this)
-            defaultTeardown(this)
-            thisTransition(this)
-        }
-
-    @PlatinumTest(focusArea = "sysui")
-    @Presubmit
-    @Test
-    fun cujCompleted() =
-        flicker.splitScreenEntered(
-            primaryApp,
-            secondaryApp,
-            fromOtherApp = false,
-            appExistAtStart = false
-        )
-
     companion object {
         @Parameterized.Parameters(name = "{0}")
         @JvmStatic
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromTaskbarBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromTaskbarBenchmark.kt
index da44ecd..78907f0 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromTaskbarBenchmark.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromTaskbarBenchmark.kt
@@ -16,21 +16,17 @@
 
 package com.android.wm.shell.flicker.splitscreen.benchmark
 
-import android.platform.test.annotations.PlatinumTest
-import android.platform.test.annotations.Presubmit
 import android.tools.common.NavBar
 import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
 import android.tools.device.flicker.legacy.FlickerBuilder
 import android.tools.device.flicker.legacy.LegacyFlickerTest
 import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
 import androidx.test.filters.RequiresDevice
-import com.android.wm.shell.flicker.splitScreenEntered
+import com.android.wm.shell.flicker.SplitScreenUtils
 import com.android.wm.shell.flicker.splitscreen.SplitScreenBase
-import com.android.wm.shell.flicker.splitscreen.SplitScreenUtils
 import org.junit.Assume
 import org.junit.Before
 import org.junit.FixMethodOrder
-import org.junit.Test
 import org.junit.runner.RunWith
 import org.junit.runners.MethodSorters
 import org.junit.runners.Parameterized
@@ -39,7 +35,7 @@
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
-open class EnterSplitScreenByDragFromTaskbarBenchmark(override val flicker: LegacyFlickerTest) :
+abstract class EnterSplitScreenByDragFromTaskbarBenchmark(override val flicker: LegacyFlickerTest) :
     SplitScreenBase(flicker) {
     protected val thisTransition: FlickerBuilder.() -> Unit
         get() = {
@@ -56,26 +52,6 @@
             }
         }
 
-    /** {@inheritDoc} */
-    override val transition: FlickerBuilder.() -> Unit
-        get() = {
-            withoutTracing(this)
-            defaultSetup(this)
-            defaultTeardown(this)
-            thisTransition(this)
-        }
-
-    @PlatinumTest(focusArea = "sysui")
-    @Presubmit
-    @Test
-    fun cujCompleted() =
-        flicker.splitScreenEntered(
-            primaryApp,
-            secondaryApp,
-            fromOtherApp = false,
-            appExistAtStart = false
-        )
-
     @Before
     fun before() {
         Assume.assumeTrue(tapl.isTablet)
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenFromOverviewBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenFromOverviewBenchmark.kt
index af06d6d..2c91e84 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenFromOverviewBenchmark.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenFromOverviewBenchmark.kt
@@ -16,18 +16,14 @@
 
 package com.android.wm.shell.flicker.splitscreen.benchmark
 
-import android.platform.test.annotations.PlatinumTest
-import android.platform.test.annotations.Presubmit
 import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
 import android.tools.device.flicker.legacy.FlickerBuilder
 import android.tools.device.flicker.legacy.LegacyFlickerTest
 import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
 import androidx.test.filters.RequiresDevice
-import com.android.wm.shell.flicker.splitScreenEntered
+import com.android.wm.shell.flicker.SplitScreenUtils
 import com.android.wm.shell.flicker.splitscreen.SplitScreenBase
-import com.android.wm.shell.flicker.splitscreen.SplitScreenUtils
 import org.junit.FixMethodOrder
-import org.junit.Test
 import org.junit.runner.RunWith
 import org.junit.runners.MethodSorters
 import org.junit.runners.Parameterized
@@ -36,7 +32,7 @@
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
-open class EnterSplitScreenFromOverviewBenchmark(override val flicker: LegacyFlickerTest) :
+abstract class EnterSplitScreenFromOverviewBenchmark(override val flicker: LegacyFlickerTest) :
     SplitScreenBase(flicker) {
     protected val thisTransition: FlickerBuilder.() -> Unit
         get() = {
@@ -56,19 +52,6 @@
             }
         }
 
-    override val transition: FlickerBuilder.() -> Unit
-        get() = {
-            withoutTracing(this)
-            defaultSetup(this)
-            defaultTeardown(this)
-            thisTransition(this)
-        }
-
-    @PlatinumTest(focusArea = "sysui")
-    @Presubmit
-    @Test
-    fun cujCompleted() = flicker.splitScreenEntered(primaryApp, secondaryApp, fromOtherApp = true)
-
     companion object {
         @Parameterized.Parameters(name = "{0}")
         @JvmStatic
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchAppByDoubleTapDividerBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchAppByDoubleTapDividerBenchmark.kt
index 23156b5..fa09c2e 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchAppByDoubleTapDividerBenchmark.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchAppByDoubleTapDividerBenchmark.kt
@@ -16,8 +16,6 @@
 
 package com.android.wm.shell.flicker.splitscreen.benchmark
 
-import android.platform.test.annotations.PlatinumTest
-import android.platform.test.annotations.Presubmit
 import android.tools.common.NavBar
 import android.tools.common.Rotation
 import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
@@ -27,10 +25,9 @@
 import android.tools.device.helpers.WindowUtils
 import android.tools.device.traces.parsers.WindowManagerStateHelper
 import androidx.test.filters.RequiresDevice
+import com.android.wm.shell.flicker.SplitScreenUtils
 import com.android.wm.shell.flicker.splitscreen.SplitScreenBase
-import com.android.wm.shell.flicker.splitscreen.SplitScreenUtils
 import org.junit.FixMethodOrder
-import org.junit.Test
 import org.junit.runner.RunWith
 import org.junit.runners.MethodSorters
 import org.junit.runners.Parameterized
@@ -39,7 +36,7 @@
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
-open class SwitchAppByDoubleTapDividerBenchmark(override val flicker: LegacyFlickerTest) :
+abstract class SwitchAppByDoubleTapDividerBenchmark(override val flicker: LegacyFlickerTest) :
     SplitScreenBase(flicker) {
     protected val thisTransition: FlickerBuilder.() -> Unit
         get() = {
@@ -53,14 +50,6 @@
             }
         }
 
-    override val transition: FlickerBuilder.() -> Unit
-        get() = {
-            withoutTracing(this)
-            defaultSetup(this)
-            defaultTeardown(this)
-            thisTransition(this)
-        }
-
     private fun waitForWindowsToSwitch(wmHelper: WindowManagerStateHelper) {
         wmHelper
             .StateSyncBuilder()
@@ -134,14 +123,6 @@
         return displayBounds.width > displayBounds.height
     }
 
-    @PlatinumTest(focusArea = "sysui")
-    @Presubmit
-    @Test
-    open fun cujCompleted() {
-        // TODO(b/246490534): Add validation for switched app after withAppTransitionIdle is
-        // robust enough to get the correct end state.
-    }
-
     companion object {
         @Parameterized.Parameters(name = "{0}")
         @JvmStatic
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBackToSplitFromAnotherAppBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBackToSplitFromAnotherAppBenchmark.kt
index 2d810d3..ff22006 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBackToSplitFromAnotherAppBenchmark.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBackToSplitFromAnotherAppBenchmark.kt
@@ -16,19 +16,15 @@
 
 package com.android.wm.shell.flicker.splitscreen.benchmark
 
-import android.platform.test.annotations.PlatinumTest
-import android.platform.test.annotations.Presubmit
 import android.tools.common.NavBar
 import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
 import android.tools.device.flicker.legacy.FlickerBuilder
 import android.tools.device.flicker.legacy.LegacyFlickerTest
 import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
 import androidx.test.filters.RequiresDevice
-import com.android.wm.shell.flicker.splitScreenEntered
+import com.android.wm.shell.flicker.SplitScreenUtils
 import com.android.wm.shell.flicker.splitscreen.SplitScreenBase
-import com.android.wm.shell.flicker.splitscreen.SplitScreenUtils
 import org.junit.FixMethodOrder
-import org.junit.Test
 import org.junit.runner.RunWith
 import org.junit.runners.MethodSorters
 import org.junit.runners.Parameterized
@@ -37,7 +33,7 @@
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
-open class SwitchBackToSplitFromAnotherAppBenchmark(override val flicker: LegacyFlickerTest) :
+abstract class SwitchBackToSplitFromAnotherAppBenchmark(override val flicker: LegacyFlickerTest) :
     SplitScreenBase(flicker) {
     private val thirdApp = SplitScreenUtils.getNonResizeable(instrumentation)
 
@@ -55,19 +51,6 @@
             }
         }
 
-    override val transition: FlickerBuilder.() -> Unit
-        get() = {
-            withoutTracing(this)
-            defaultSetup(this)
-            defaultTeardown(this)
-            thisTransition(this)
-        }
-
-    @PlatinumTest(focusArea = "sysui")
-    @Presubmit
-    @Test
-    fun cujCompleted() = flicker.splitScreenEntered(primaryApp, secondaryApp, fromOtherApp = true)
-
     companion object {
         @Parameterized.Parameters(name = "{0}")
         @JvmStatic
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBackToSplitFromHomeBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBackToSplitFromHomeBenchmark.kt
index f6df1e4..5787b02 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBackToSplitFromHomeBenchmark.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBackToSplitFromHomeBenchmark.kt
@@ -16,19 +16,15 @@
 
 package com.android.wm.shell.flicker.splitscreen.benchmark
 
-import android.platform.test.annotations.PlatinumTest
-import android.platform.test.annotations.Presubmit
 import android.tools.common.NavBar
 import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
 import android.tools.device.flicker.legacy.FlickerBuilder
 import android.tools.device.flicker.legacy.LegacyFlickerTest
 import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
 import androidx.test.filters.RequiresDevice
-import com.android.wm.shell.flicker.splitScreenEntered
+import com.android.wm.shell.flicker.SplitScreenUtils
 import com.android.wm.shell.flicker.splitscreen.SplitScreenBase
-import com.android.wm.shell.flicker.splitscreen.SplitScreenUtils
 import org.junit.FixMethodOrder
-import org.junit.Test
 import org.junit.runner.RunWith
 import org.junit.runners.MethodSorters
 import org.junit.runners.Parameterized
@@ -37,7 +33,7 @@
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
-open class SwitchBackToSplitFromHomeBenchmark(override val flicker: LegacyFlickerTest) :
+abstract class SwitchBackToSplitFromHomeBenchmark(override val flicker: LegacyFlickerTest) :
     SplitScreenBase(flicker) {
     protected val thisTransition: FlickerBuilder.() -> Unit
         get() = {
@@ -53,19 +49,6 @@
             }
         }
 
-    override val transition: FlickerBuilder.() -> Unit
-        get() = {
-            withoutTracing(this)
-            defaultSetup(this)
-            defaultTeardown(this)
-            thisTransition(this)
-        }
-
-    @PlatinumTest(focusArea = "sysui")
-    @Presubmit
-    @Test
-    fun cujCompleted() = flicker.splitScreenEntered(primaryApp, secondaryApp, fromOtherApp = true)
-
     companion object {
         @Parameterized.Parameters(name = "{0}")
         @JvmStatic
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBackToSplitFromRecentBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBackToSplitFromRecentBenchmark.kt
index ba46bdc..b2d5091 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBackToSplitFromRecentBenchmark.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBackToSplitFromRecentBenchmark.kt
@@ -16,19 +16,15 @@
 
 package com.android.wm.shell.flicker.splitscreen.benchmark
 
-import android.platform.test.annotations.PlatinumTest
-import android.platform.test.annotations.Presubmit
 import android.tools.common.NavBar
 import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
 import android.tools.device.flicker.legacy.FlickerBuilder
 import android.tools.device.flicker.legacy.LegacyFlickerTest
 import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
 import androidx.test.filters.RequiresDevice
-import com.android.wm.shell.flicker.splitScreenEntered
+import com.android.wm.shell.flicker.SplitScreenUtils
 import com.android.wm.shell.flicker.splitscreen.SplitScreenBase
-import com.android.wm.shell.flicker.splitscreen.SplitScreenUtils
 import org.junit.FixMethodOrder
-import org.junit.Test
 import org.junit.runner.RunWith
 import org.junit.runners.MethodSorters
 import org.junit.runners.Parameterized
@@ -37,7 +33,7 @@
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
-open class SwitchBackToSplitFromRecentBenchmark(override val flicker: LegacyFlickerTest) :
+abstract class SwitchBackToSplitFromRecentBenchmark(override val flicker: LegacyFlickerTest) :
     SplitScreenBase(flicker) {
     protected val thisTransition: FlickerBuilder.() -> Unit
         get() = {
@@ -53,19 +49,6 @@
             }
         }
 
-    override val transition: FlickerBuilder.() -> Unit
-        get() = {
-            withoutTracing(this)
-            defaultSetup(this)
-            defaultTeardown(this)
-            thisTransition(this)
-        }
-
-    @PlatinumTest(focusArea = "sysui")
-    @Presubmit
-    @Test
-    fun cujCompleted() = flicker.splitScreenEntered(primaryApp, secondaryApp, fromOtherApp = true)
-
     companion object {
         @Parameterized.Parameters(name = "{0}")
         @JvmStatic
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBetweenSplitPairsBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBetweenSplitPairsBenchmark.kt
index 0d871e5..f234e46 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBetweenSplitPairsBenchmark.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBetweenSplitPairsBenchmark.kt
@@ -16,17 +16,14 @@
 
 package com.android.wm.shell.flicker.splitscreen.benchmark
 
-import android.platform.test.annotations.PlatinumTest
-import android.platform.test.annotations.Presubmit
 import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
 import android.tools.device.flicker.legacy.FlickerBuilder
 import android.tools.device.flicker.legacy.LegacyFlickerTest
 import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
 import androidx.test.filters.RequiresDevice
+import com.android.wm.shell.flicker.SplitScreenUtils
 import com.android.wm.shell.flicker.splitscreen.SplitScreenBase
-import com.android.wm.shell.flicker.splitscreen.SplitScreenUtils
 import org.junit.FixMethodOrder
-import org.junit.Test
 import org.junit.runner.RunWith
 import org.junit.runners.MethodSorters
 import org.junit.runners.Parameterized
@@ -35,7 +32,7 @@
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
-open class SwitchBetweenSplitPairsBenchmark(override val flicker: LegacyFlickerTest) :
+abstract class SwitchBetweenSplitPairsBenchmark(override val flicker: LegacyFlickerTest) :
     SplitScreenBase(flicker) {
     protected val thirdApp = SplitScreenUtils.getIme(instrumentation)
     protected val fourthApp = SplitScreenUtils.getSendNotification(instrumentation)
@@ -64,9 +61,6 @@
             thisTransition(this)
         }
 
-    @PlatinumTest(focusArea = "sysui")
-    @Presubmit @Test open fun cujCompleted() {}
-
     companion object {
         @Parameterized.Parameters(name = "{0}")
         @JvmStatic
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/UnlockKeyguardToSplitScreenBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/UnlockKeyguardToSplitScreenBenchmark.kt
index 7952b71..61c3679 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/UnlockKeyguardToSplitScreenBenchmark.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/UnlockKeyguardToSplitScreenBenchmark.kt
@@ -22,8 +22,8 @@
 import android.tools.device.flicker.legacy.LegacyFlickerTest
 import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
 import androidx.test.filters.RequiresDevice
+import com.android.wm.shell.flicker.SplitScreenUtils
 import com.android.wm.shell.flicker.splitscreen.SplitScreenBase
-import com.android.wm.shell.flicker.splitscreen.SplitScreenUtils
 import org.junit.FixMethodOrder
 import org.junit.runner.RunWith
 import org.junit.runners.MethodSorters
@@ -33,7 +33,7 @@
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
-open class UnlockKeyguardToSplitScreenBenchmark(override val flicker: LegacyFlickerTest) :
+abstract class UnlockKeyguardToSplitScreenBenchmark(override val flicker: LegacyFlickerTest) :
     SplitScreenBase(flicker) {
     protected val thisTransition: FlickerBuilder.() -> Unit
         get() = {
@@ -47,14 +47,6 @@
             }
         }
 
-    /** {@inheritDoc} */
-    override val transition: FlickerBuilder.() -> Unit
-        get() = {
-            defaultSetup(this)
-            defaultTeardown(this)
-            thisTransition(this)
-        }
-
     companion object {
         @Parameterized.Parameters(name = "{0}")
         @JvmStatic
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/EnterDesktopTaskTransitionHandlerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/EnterDesktopTaskTransitionHandlerTest.java
index 8592dea..c6642f3 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/EnterDesktopTaskTransitionHandlerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/EnterDesktopTaskTransitionHandlerTest.java
@@ -28,6 +28,7 @@
 import android.annotation.NonNull;
 import android.app.ActivityManager;
 import android.app.WindowConfiguration;
+import android.graphics.PointF;
 import android.graphics.Rect;
 import android.os.IBinder;
 import android.view.SurfaceControl;
@@ -41,6 +42,7 @@
 
 import com.android.wm.shell.common.ShellExecutor;
 import com.android.wm.shell.transition.Transitions;
+import com.android.wm.shell.windowdecor.MoveToDesktopAnimator;
 
 import junit.framework.AssertionFailedError;
 
@@ -73,6 +75,10 @@
     ShellExecutor mExecutor;
     @Mock
     SurfaceControl mSurfaceControl;
+    @Mock
+    MoveToDesktopAnimator mMoveToDesktopAnimator;
+    @Mock
+    PointF mPosition;
 
     private EnterDesktopTaskTransitionHandler mEnterDesktopTaskTransitionHandler;
 
@@ -82,6 +88,7 @@
 
         doReturn(mExecutor).when(mTransitions).getMainExecutor();
         doReturn(mAnimationT).when(mTransactionFactory).get();
+        doReturn(mPosition).when(mMoveToDesktopAnimator).getPosition();
 
         mEnterDesktopTaskTransitionHandler = new EnterDesktopTaskTransitionHandler(mTransitions,
                 mTransactionFactory);
@@ -89,12 +96,15 @@
 
     @Test
     public void testEnterFreeformAnimation() {
-        final int transitionType = Transitions.TRANSIT_ENTER_FREEFORM;
         final int taskId = 1;
         WindowContainerTransaction wct = new WindowContainerTransaction();
         doReturn(mToken).when(mTransitions)
-                .startTransition(transitionType, wct, mEnterDesktopTaskTransitionHandler);
-        mEnterDesktopTaskTransitionHandler.startTransition(transitionType, wct, null);
+                .startTransition(Transitions.TRANSIT_ENTER_FREEFORM, wct,
+                        mEnterDesktopTaskTransitionHandler);
+        doReturn(taskId).when(mMoveToDesktopAnimator).getTaskId();
+
+        mEnterDesktopTaskTransitionHandler.startMoveToFreeformAnimation(wct,
+                mMoveToDesktopAnimator, null);
 
         TransitionInfo.Change change =
                 createChange(WindowManager.TRANSIT_CHANGE, taskId, WINDOWING_MODE_FREEFORM);
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageTaskListenerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageTaskListenerTests.java
index df1e2e1..946a7ef 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageTaskListenerTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageTaskListenerTests.java
@@ -161,7 +161,7 @@
         childTask.supportsMultiWindow = false;
 
         mStageTaskListener.onTaskInfoChanged(childTask);
-        verify(mCallbacks).onNoLongerSupportMultiWindow();
+        verify(mCallbacks).onNoLongerSupportMultiWindow(childTask);
     }
 
     @Test
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/taskview/TaskViewTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/taskview/TaskViewTest.java
index 1b38956..50435a0 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/taskview/TaskViewTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/taskview/TaskViewTest.java
@@ -40,6 +40,7 @@
 import android.app.ActivityOptions;
 import android.app.PendingIntent;
 import android.content.Context;
+import android.graphics.Insets;
 import android.graphics.Rect;
 import android.graphics.Region;
 import android.testing.AndroidTestingRunner;
@@ -563,4 +564,47 @@
         mTaskViewTaskController.onTaskAppeared(mTaskInfo, mLeash);
         verify(mTaskViewTaskController, never()).cleanUpPendingTask();
     }
+
+    @Test
+    public void testSetCaptionInsets_noTaskInitially() {
+        assumeTrue(Transitions.ENABLE_SHELL_TRANSITIONS);
+
+        Rect insets = new Rect(0, 400, 0, 0);
+        mTaskView.setCaptionInsets(Insets.of(insets));
+        mTaskView.onComputeInternalInsets(new ViewTreeObserver.InternalInsetsInfo());
+
+        verify(mTaskViewTaskController).applyCaptionInsetsIfNeeded();
+        verify(mOrganizer, never()).applyTransaction(any());
+
+        mTaskView.surfaceCreated(mock(SurfaceHolder.class));
+        reset(mOrganizer);
+        reset(mTaskViewTaskController);
+        WindowContainerTransaction wct = new WindowContainerTransaction();
+        mTaskViewTaskController.prepareOpenAnimation(true /* newTask */,
+                new SurfaceControl.Transaction(), new SurfaceControl.Transaction(), mTaskInfo,
+                mLeash, wct);
+        mTaskView.onComputeInternalInsets(new ViewTreeObserver.InternalInsetsInfo());
+
+        verify(mTaskViewTaskController).applyCaptionInsetsIfNeeded();
+        verify(mOrganizer).applyTransaction(any());
+    }
+
+    @Test
+    public void testSetCaptionInsets_withTask() {
+        assumeTrue(Transitions.ENABLE_SHELL_TRANSITIONS);
+
+        mTaskView.surfaceCreated(mock(SurfaceHolder.class));
+        WindowContainerTransaction wct = new WindowContainerTransaction();
+        mTaskViewTaskController.prepareOpenAnimation(true /* newTask */,
+                new SurfaceControl.Transaction(), new SurfaceControl.Transaction(), mTaskInfo,
+                mLeash, wct);
+        reset(mTaskViewTaskController);
+        reset(mOrganizer);
+
+        Rect insets = new Rect(0, 400, 0, 0);
+        mTaskView.setCaptionInsets(Insets.of(insets));
+        mTaskView.onComputeInternalInsets(new ViewTreeObserver.InternalInsetsInfo());
+        verify(mTaskViewTaskController).applyCaptionInsetsIfNeeded();
+        verify(mOrganizer).applyTransaction(any());
+    }
 }
diff --git a/media/java/android/media/projection/IMediaProjectionWatcherCallback.aidl b/media/java/android/media/projection/IMediaProjectionWatcherCallback.aidl
index 2231ce1..e46d34e 100644
--- a/media/java/android/media/projection/IMediaProjectionWatcherCallback.aidl
+++ b/media/java/android/media/projection/IMediaProjectionWatcherCallback.aidl
@@ -17,9 +17,22 @@
 package android.media.projection;
 
 import android.media.projection.MediaProjectionInfo;
+import android.view.ContentRecordingSession;
 
 /** {@hide} */
 oneway interface IMediaProjectionWatcherCallback {
     void onStart(in MediaProjectionInfo info);
     void onStop(in MediaProjectionInfo info);
+    /**
+     * Called when the {@link ContentRecordingSession} was set for the current media
+     * projection.
+     *
+     * @param info    always present and contains information about the media projection host.
+     * @param session the recording session for the current media projection. Can be
+     *                {@code null} when the recording will stop.
+     */
+    void onRecordingSessionSet(
+        in MediaProjectionInfo info,
+        in @nullable ContentRecordingSession session
+    );
 }
diff --git a/media/java/android/media/projection/MediaProjectionManager.java b/media/java/android/media/projection/MediaProjectionManager.java
index 5703c42..5a68c53 100644
--- a/media/java/android/media/projection/MediaProjectionManager.java
+++ b/media/java/android/media/projection/MediaProjectionManager.java
@@ -29,6 +29,7 @@
 import android.os.ServiceManager;
 import android.util.ArrayMap;
 import android.util.Log;
+import android.view.ContentRecordingSession;
 import android.view.Surface;
 
 import java.util.Map;
@@ -300,7 +301,22 @@
     /** @hide */
     public static abstract class Callback {
         public abstract void onStart(MediaProjectionInfo info);
+
         public abstract void onStop(MediaProjectionInfo info);
+
+        /**
+         * Called when the {@link ContentRecordingSession} was set for the current media
+         * projection.
+         *
+         * @param info    always present and contains information about the media projection host.
+         * @param session the recording session for the current media projection. Can be
+         *                {@code null} when the recording will stop.
+         */
+        public void onRecordingSessionSet(
+                @NonNull MediaProjectionInfo info,
+                @Nullable ContentRecordingSession session
+        ) {
+        }
     }
 
     /** @hide */
@@ -335,5 +351,13 @@
                 }
             });
         }
+
+        @Override
+        public void onRecordingSessionSet(
+                @NonNull final MediaProjectionInfo info,
+                @Nullable final ContentRecordingSession session
+        ) {
+            mHandler.post(() -> mCallback.onRecordingSessionSet(info, session));
+        }
     }
 }
diff --git a/packages/CarrierDefaultApp/assets/slice_purchase_test.html b/packages/CarrierDefaultApp/assets/slice_purchase_test.html
index 917276b..ad18a9d 100644
--- a/packages/CarrierDefaultApp/assets/slice_purchase_test.html
+++ b/packages/CarrierDefaultApp/assets/slice_purchase_test.html
@@ -81,5 +81,7 @@
         Dismiss flow
     </button>
     <p id="dismiss_flow"></p>
+
+    <h2>Test <a href="http://www.google.com">hyperlink</a></h2>
 </body>
 </html>
diff --git a/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/SlicePurchaseActivity.java b/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/SlicePurchaseActivity.java
index 2530257d6..b100980 100644
--- a/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/SlicePurchaseActivity.java
+++ b/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/SlicePurchaseActivity.java
@@ -29,6 +29,7 @@
 import android.view.KeyEvent;
 import android.webkit.CookieManager;
 import android.webkit.WebView;
+import android.webkit.WebViewClient;
 
 import com.android.phone.slice.SlicePurchaseController;
 
@@ -113,8 +114,10 @@
             return;
         }
 
-        // Create and configure WebView
-        setupWebView();
+        // Clear any cookies that might be persisted from previous sessions before loading WebView
+        CookieManager.getInstance().removeAllCookies(value -> {
+            setupWebView();
+        });
     }
 
     protected void onPurchaseSuccessful() {
@@ -176,12 +179,7 @@
     private void setupWebView() {
         // Create WebView
         mWebView = new WebView(this);
-
-        // Clear any cookies and state that might be saved from previous sessions
-        CookieManager.getInstance().removeAllCookies(null);
-        CookieManager.getInstance().flush();
-        mWebView.clearCache(true);
-        mWebView.clearHistory();
+        mWebView.setWebViewClient(new WebViewClient());
 
         // Enable JavaScript for the carrier purchase website to send results back to
         //  the slice purchase application.
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/RestrictedSwitchPreference.java b/packages/SettingsLib/src/com/android/settingslib/RestrictedSwitchPreference.java
index b5e4fa3..af06d73 100644
--- a/packages/SettingsLib/src/com/android/settingslib/RestrictedSwitchPreference.java
+++ b/packages/SettingsLib/src/com/android/settingslib/RestrictedSwitchPreference.java
@@ -243,7 +243,9 @@
         return mHelper != null ? mHelper.packageName : null;
     }
 
-    public void updateState(@NonNull String packageName, int uid, boolean isEnabled) {
+    /** Updates enabled state based on associated package. */
+    public void updateState(
+            @NonNull String packageName, int uid, boolean isEnableAllowed, boolean isEnabled) {
         mHelper.updatePackageDetails(packageName, uid);
         if (mAppOpsManager == null) {
             mAppOpsManager = getContext().getSystemService(AppOpsManager.class);
@@ -254,7 +256,9 @@
         final boolean ecmEnabled = getContext().getResources().getBoolean(
                 com.android.internal.R.bool.config_enhancedConfirmationModeEnabled);
         final boolean appOpsAllowed = !ecmEnabled || mode == AppOpsManager.MODE_ALLOWED;
-        if (isEnabled) {
+        if (!isEnableAllowed && !isEnabled) {
+            setEnabled(false);
+        } else if (isEnabled) {
             setEnabled(true);
         } else if (appOpsAllowed && isDisabledByAppOps()) {
             setEnabled(true);
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java
index 2e6bb53..f522fd1 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java
@@ -583,7 +583,7 @@
      */
     public void setName(String name) {
         // Prevent getName() to be set to null if setName(null) is called
-        if (name == null || TextUtils.equals(name, getName())) {
+        if (TextUtils.isEmpty(name) || TextUtils.equals(name, getName())) {
             return;
         }
         mDevice.setAlias(name);
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/BluetoothMediaDevice.java b/packages/SettingsLib/src/com/android/settingslib/media/BluetoothMediaDevice.java
index cd6609e..963bd9d 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/BluetoothMediaDevice.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/BluetoothMediaDevice.java
@@ -15,6 +15,8 @@
  */
 package com.android.settingslib.media;
 
+import static com.android.settingslib.media.MediaDevice.SelectionBehavior.SELECTION_BEHAVIOR_TRANSFER;
+
 import android.bluetooth.BluetoothClass;
 import android.bluetooth.BluetoothDevice;
 import android.content.Context;
@@ -22,6 +24,7 @@
 import android.media.AudioManager;
 import android.media.MediaRoute2Info;
 import android.media.MediaRouter2Manager;
+import android.media.RouteListingPreference;
 
 import com.android.settingslib.R;
 import com.android.settingslib.bluetooth.BluetoothUtils;
@@ -39,7 +42,13 @@
 
     BluetoothMediaDevice(Context context, CachedBluetoothDevice device,
             MediaRouter2Manager routerManager, MediaRoute2Info info, String packageName) {
-        super(context, routerManager, info, packageName, null);
+        this(context, device, routerManager, info, packageName, null);
+    }
+
+    BluetoothMediaDevice(Context context, CachedBluetoothDevice device,
+            MediaRouter2Manager routerManager, MediaRoute2Info info, String packageName,
+            RouteListingPreference.Item item) {
+        super(context, routerManager, info, packageName, item);
         mCachedDevice = device;
         mAudioManager = context.getSystemService(AudioManager.class);
         initDeviceRecord();
@@ -58,6 +67,12 @@
     }
 
     @Override
+    public int getSelectionBehavior() {
+        // We don't allow apps to override the selection behavior of system routes.
+        return SELECTION_BEHAVIOR_TRANSFER;
+    }
+
+    @Override
     public Drawable getIcon() {
         return BluetoothUtils.isAdvancedUntetheredDevice(mCachedDevice.getDevice())
                 ? mContext.getDrawable(R.drawable.ic_earbuds_advanced)
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java b/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java
index 2a486a9..1728e40 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java
@@ -568,8 +568,10 @@
             case TYPE_HDMI:
             case TYPE_WIRED_HEADSET:
             case TYPE_WIRED_HEADPHONES:
-                mediaDevice =
-                        new PhoneMediaDevice(mContext, mRouterManager, route, mPackageName);
+                mediaDevice = mPreferenceItemMap.containsKey(route.getId()) ? new PhoneMediaDevice(
+                        mContext, mRouterManager, route, mPackageName,
+                        mPreferenceItemMap.get(route.getId())) : new PhoneMediaDevice(mContext,
+                        mRouterManager, route, mPackageName);
                 break;
             case TYPE_HEARING_AID:
             case TYPE_BLUETOOTH_A2DP:
@@ -579,8 +581,11 @@
                 final CachedBluetoothDevice cachedDevice =
                         mBluetoothManager.getCachedDeviceManager().findDevice(device);
                 if (cachedDevice != null) {
-                    mediaDevice = new BluetoothMediaDevice(mContext, cachedDevice, mRouterManager,
-                            route, mPackageName);
+                    mediaDevice = mPreferenceItemMap.containsKey(route.getId())
+                            ? new BluetoothMediaDevice(mContext, cachedDevice, mRouterManager,
+                            route, mPackageName, mPreferenceItemMap.get(route.getId()))
+                            : new BluetoothMediaDevice(mContext, cachedDevice, mRouterManager,
+                                    route, mPackageName);
                 }
                 break;
             case TYPE_REMOTE_AUDIO_VIDEO_RECEIVER:
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/PhoneMediaDevice.java b/packages/SettingsLib/src/com/android/settingslib/media/PhoneMediaDevice.java
index 34519c9..accd88c 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/PhoneMediaDevice.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/PhoneMediaDevice.java
@@ -24,10 +24,13 @@
 import static android.media.MediaRoute2Info.TYPE_WIRED_HEADPHONES;
 import static android.media.MediaRoute2Info.TYPE_WIRED_HEADSET;
 
+import static com.android.settingslib.media.MediaDevice.SelectionBehavior.SELECTION_BEHAVIOR_TRANSFER;
+
 import android.content.Context;
 import android.graphics.drawable.Drawable;
 import android.media.MediaRoute2Info;
 import android.media.MediaRouter2Manager;
+import android.media.RouteListingPreference;
 
 import androidx.annotation.VisibleForTesting;
 
@@ -51,7 +54,12 @@
 
     PhoneMediaDevice(Context context, MediaRouter2Manager routerManager, MediaRoute2Info info,
             String packageName) {
-        super(context, routerManager, info, packageName, null);
+        this(context, routerManager, info, packageName, null);
+    }
+
+    PhoneMediaDevice(Context context, MediaRouter2Manager routerManager, MediaRoute2Info info,
+            String packageName, RouteListingPreference.Item item) {
+        super(context, routerManager, info, packageName, item);
         mDeviceIconUtil = new DeviceIconUtil();
         initDeviceRecord();
     }
@@ -86,6 +94,12 @@
     }
 
     @Override
+    public int getSelectionBehavior() {
+        // We don't allow apps to override the selection behavior of system routes.
+        return SELECTION_BEHAVIOR_TRANSFER;
+    }
+
+    @Override
     public String getSummary() {
         return mSummary;
     }
diff --git a/packages/SettingsLib/tests/robotests/Android.bp b/packages/SettingsLib/tests/robotests/Android.bp
index 5c55a43..c037c40 100644
--- a/packages/SettingsLib/tests/robotests/Android.bp
+++ b/packages/SettingsLib/tests/robotests/Android.bp
@@ -42,7 +42,10 @@
     name: "SettingsLibRoboTests",
     srcs: ["src/**/*.java"],
     static_libs: [
+        "Settings_robolectric_meta_service_file",
+        "Robolectric_shadows_androidx_fragment_upstream",
         "SettingsLib-robo-testutils",
+        "androidx.fragment_fragment",
         "androidx.test.core",
         "androidx.core_core",
         "testng", // TODO: remove once JUnit on Android provides assertThrows
@@ -53,6 +56,20 @@
     test_options: {
         timeout: 36000,
     },
+    upstream: true,
+}
+
+java_genrule {
+    name: "Settings_robolectric_meta_service_file",
+    out: ["robolectric_meta_service_file.jar"],
+    tools: ["soong_zip"],
+    cmd: "mkdir -p $(genDir)/META-INF/services/ && touch $(genDir)/META-INF/services/org.robolectric.internal.ShadowProvider &&" +
+        "echo -e 'org.robolectric.Shadows' >> $(genDir)/META-INF/services/org.robolectric.internal.ShadowProvider && " +
+        "echo -e 'org.robolectric.shadows.multidex.Shadows' >> $(genDir)/META-INF/services/org.robolectric.internal.ShadowProvider && " +
+        "echo -e 'org.robolectric.shadows.httpclient.Shadows' >> $(genDir)/META-INF/services/org.robolectric.internal.ShadowProvider && " +
+        //"echo -e 'com.android.settings.testutils.shadow.Shadows' >> $(genDir)/META-INF/services/org.robolectric.internal.ShadowProvider && " +
+        "echo -e 'com.android.settingslib.testutils.shadow.Shadows' >> $(genDir)/META-INF/services/org.robolectric.internal.ShadowProvider && " +
+        "$(location soong_zip) -o $(out) -C $(genDir) -D $(genDir)/META-INF/services/",
 }
 
 java_library {
@@ -60,9 +77,23 @@
     srcs: [
         "testutils/com/android/settingslib/testutils/**/*.java",
     ],
-
+    javacflags: [
+        "-Aorg.robolectric.annotation.processing.shadowPackage=com.android.settingslib.testutils.shadow",
+        "-Aorg.robolectric.annotation.processing.sdkCheckMode=ERROR",
+        // Uncomment the below to debug annotation processors not firing.
+        //"-verbose",
+        //"-XprintRounds",
+        //"-XprintProcessorInfo",
+        //"-Xlint",
+        //"-J-verbose",
+    ],
+    plugins: [
+        "auto_value_plugin_1.9",
+        "auto_value_builder_plugin_1.9",
+        "Robolectric_processor_upstream",
+    ],
     libs: [
-        "Robolectric_all-target",
+        "Robolectric_all-target_upstream",
         "mockito-robolectric-prebuilt",
         "truth-prebuilt",
     ],
diff --git a/packages/SettingsLib/tests/robotests/config/robolectric.properties b/packages/SettingsLib/tests/robotests/config/robolectric.properties
index fab7251..2a9e50d 100644
--- a/packages/SettingsLib/tests/robotests/config/robolectric.properties
+++ b/packages/SettingsLib/tests/robotests/config/robolectric.properties
@@ -1 +1,2 @@
 sdk=NEWEST_SDK
+instrumentedPackages=androidx.preference
diff --git a/packages/SettingsLib/tests/robotests/fragment/Android.bp b/packages/SettingsLib/tests/robotests/fragment/Android.bp
new file mode 100644
index 0000000..3e67156
--- /dev/null
+++ b/packages/SettingsLib/tests/robotests/fragment/Android.bp
@@ -0,0 +1,40 @@
+//#############################################
+// Compile Robolectric shadows framework misapplied to androidx
+//#############################################
+
+package {
+    // See: http://go/android-license-faq
+    // A large-scale-change added 'default_applicable_licenses' to import
+    // all of the 'license_kinds' from "frameworks_base_license"
+    // to get the below license kinds:
+    //   SPDX-license-identifier-Apache-2.0
+    default_applicable_licenses: ["frameworks_base_license"],
+}
+
+java_library {
+    name: "Robolectric_shadows_androidx_fragment_upstream",
+    srcs: [
+        "src/main/java/**/*.java",
+        "src/main/java/**/*.kt",
+    ],
+    javacflags: [
+        "-Aorg.robolectric.annotation.processing.shadowPackage=org.robolectric.shadows.androidx.fragment",
+        "-Aorg.robolectric.annotation.processing.sdkCheckMode=ERROR",
+        // Uncomment the below to debug annotation processors not firing.
+        //"-verbose",
+        //"-XprintRounds",
+        //"-XprintProcessorInfo",
+        //"-Xlint",
+        //"-J-verbose",
+    ],
+    libs: [
+        "Robolectric_all-target_upstream",
+        "androidx.fragment_fragment",
+    ],
+    plugins: [
+        "auto_value_plugin_1.9",
+        "auto_value_builder_plugin_1.9",
+        "Robolectric_processor_upstream",
+    ],
+
+}
diff --git a/packages/SettingsLib/tests/robotests/fragment/BUILD b/packages/SettingsLib/tests/robotests/fragment/BUILD
new file mode 100644
index 0000000..393a02e
--- /dev/null
+++ b/packages/SettingsLib/tests/robotests/fragment/BUILD
@@ -0,0 +1,69 @@
+load("//third_party/java/android/android_sdk_linux/extras/android/compatibility/jetify:jetify.bzl", "jetify_android_library", "jetify_android_local_test")
+
+package(
+    default_applicable_licenses = ["//third_party/java_src/robolectric:license"],
+    default_visibility = ["//third_party/java_src/robolectric:__subpackages__"],
+)
+
+licenses(["notice"])
+
+#==============================================================================
+# Test resources library
+#==============================================================================
+jetify_android_library(
+    name = "test_resources",
+    custom_package = "org.robolectric.shadows.androidx.fragment",
+    manifest = "src/test/AndroidManifest.xml",
+    resource_files = glob(
+        ["src/test/resources/**/*"],
+    ),
+)
+
+#==============================================================================
+# AndroidX fragment module library
+#==============================================================================
+jetify_android_library(
+    name = "androidx_fragment",
+    testonly = 1,
+    srcs = glob(
+        ["src/main/java/**"],
+    ),
+    custom_package = "org.robolectric.shadows.androidx.fragment",
+    javacopts = [
+        "-Aorg.robolectric.annotation.processing.shadowPackage=org.robolectric.shadows.androidx.fragment",
+    ],
+    jetify_sources = True,
+    plugins = [
+        "//java/com/google/thirdparty/robolectric/processor",
+    ],
+    deps = [
+        "//third_party/java/androidx/core",
+        "//third_party/java/androidx/fragment",
+        "//third_party/java/androidx/lifecycle",
+        "//third_party/java_src/robolectric/shadowapi",
+        "//third_party/java_src/robolectric/shadows/framework",
+    ],
+)
+
+[
+    jetify_android_local_test(
+        name = "test_" + src.rstrip(".java"),
+        size = "small",
+        srcs = glob(
+            ["src/test/java/**/*.java"],
+        ),
+        jetify_sources = True,
+        deps = [
+            ":androidx_fragment",
+            ":test_resources",
+            "//third_party/java/androidx/fragment",
+            "//third_party/java/androidx/loader",
+            "//third_party/java/mockito",
+            "//third_party/java/robolectric",
+            "//third_party/java/truth",
+        ],
+    )
+    for src in glob(
+        ["src/test/java/**/*Test.java"],
+    )
+]
diff --git a/packages/SettingsLib/tests/robotests/fragment/build.gradle b/packages/SettingsLib/tests/robotests/fragment/build.gradle
new file mode 100644
index 0000000..d9dcd84
--- /dev/null
+++ b/packages/SettingsLib/tests/robotests/fragment/build.gradle
@@ -0,0 +1,48 @@
+plugins {
+    id "net.ltgt.errorprone" version "0.0.13"
+}
+
+apply plugin: 'com.android.library'
+
+android {
+    compileSdkVersion 28
+
+    android {
+        sourceSets {
+            main {
+                res.srcDirs = ['src/test/resources/res']
+            }
+        }
+        testOptions {
+            unitTests {
+                includeAndroidResources = true
+            }
+        }
+    }
+}
+
+dependencies {
+    // Project dependencies
+    compileOnly project(":robolectric")
+
+    // Compile dependencies
+    compileOnly AndroidSdk.MAX_SDK.coordinates
+    compileOnly "androidx.core:core:1.0.0-rc02"
+    compileOnly 'androidx.fragment:fragment:1.0.0-rc02'
+    compileOnly "androidx.lifecycle:lifecycle-viewmodel:2.0.0-rc01"
+    compileOnly "androidx.lifecycle:lifecycle-common:2.0.0-beta01"
+
+    // Testing dependencies
+    testImplementation "com.google.truth:truth:0.44"
+    testImplementation "org.mockito:mockito-core:2.5.4"
+    testImplementation "androidx.arch.core:core-common:2.0.0-beta01"
+    testImplementation "androidx.arch.core:core-runtime:2.0.0-rc01"
+    testImplementation "androidx.collection:collection:1.0.0-rc01"
+    testImplementation "androidx.core:core:1.0.0-rc02"
+    testImplementation 'androidx.fragment:fragment:1.0.0-rc02'
+    testImplementation "androidx.lifecycle:lifecycle-viewmodel:2.0.0-rc01"
+    testImplementation "androidx.lifecycle:lifecycle-common:2.0.0-beta01"
+    testImplementation "androidx.lifecycle:lifecycle-runtime:2.0.0-rc01"
+    testImplementation "androidx.lifecycle:lifecycle-livedata-core:2.0.0-rc01"
+    testImplementation "androidx.loader:loader:1.0.0-rc02"
+}
diff --git a/packages/SettingsLib/tests/robotests/fragment/src/main/java/org/robolectric/shadows/androidx/fragment/FragmentController.java b/packages/SettingsLib/tests/robotests/fragment/src/main/java/org/robolectric/shadows/androidx/fragment/FragmentController.java
new file mode 100644
index 0000000..c688683
--- /dev/null
+++ b/packages/SettingsLib/tests/robotests/fragment/src/main/java/org/robolectric/shadows/androidx/fragment/FragmentController.java
@@ -0,0 +1,348 @@
+/*
+ * 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 org.robolectric.shadows.androidx.fragment;
+
+import android.content.Intent;
+import android.os.Bundle;
+import android.widget.LinearLayout;
+
+import androidx.fragment.app.Fragment;
+import androidx.fragment.app.FragmentActivity;
+
+import org.robolectric.android.controller.ActivityController;
+import org.robolectric.android.controller.ComponentController;
+import org.robolectric.util.ReflectionHelpers;
+
+/** A Controller that can be used to drive the lifecycle of a {@link Fragment} */
+public class FragmentController<F extends Fragment>
+        extends ComponentController<FragmentController<F>, F> {
+
+    private final F mFragment;
+    private final ActivityController<? extends FragmentActivity> mActivityController;
+
+    private FragmentController(F fragment, Class<? extends FragmentActivity> activityClass) {
+        this(fragment, activityClass, null /*intent*/, null /*arguments*/);
+    }
+
+    private FragmentController(
+            F fragment, Class<? extends FragmentActivity> activityClass, Intent intent) {
+        this(fragment, activityClass, intent, null /*arguments*/);
+    }
+
+    private FragmentController(
+            F fragment, Class<? extends FragmentActivity> activityClass, Bundle arguments) {
+        this(fragment, activityClass, null /*intent*/, arguments);
+    }
+
+    private FragmentController(
+            F fragment,
+            Class<? extends FragmentActivity> activityClass,
+            Intent intent,
+            Bundle arguments) {
+        super(fragment, intent);
+        this.mFragment = fragment;
+        if (arguments != null) {
+            this.mFragment.setArguments(arguments);
+        }
+        this.mActivityController =
+                ActivityController.of(ReflectionHelpers.callConstructor(activityClass), intent);
+    }
+
+    /**
+     * Generate the {@link FragmentController} for specific fragment.
+     *
+     * @param fragment the fragment which you'd like to drive lifecycle
+     * @return {@link FragmentController}
+     */
+    public static <F extends Fragment> FragmentController<F> of(F fragment) {
+        return new FragmentController<>(fragment, FragmentControllerActivity.class);
+    }
+
+    /**
+     * Generate the {@link FragmentController} for specific fragment and intent.
+     *
+     * @param fragment the fragment which you'd like to drive lifecycle
+     * @param intent   the intent which will be retained by activity
+     * @return {@link FragmentController}
+     */
+    public static <F extends Fragment> FragmentController<F> of(F fragment, Intent intent) {
+        return new FragmentController<>(fragment, FragmentControllerActivity.class, intent);
+    }
+
+    /**
+     * Generate the {@link FragmentController} for specific fragment and arguments.
+     *
+     * @param fragment  the fragment which you'd like to drive lifecycle
+     * @param arguments the arguments which will be retained by fragment
+     * @return {@link FragmentController}
+     */
+    public static <F extends Fragment> FragmentController<F> of(F fragment, Bundle arguments) {
+        return new FragmentController<>(fragment, FragmentControllerActivity.class, arguments);
+    }
+
+    /**
+     * Generate the {@link FragmentController} for specific fragment and activity class.
+     *
+     * @param fragment      the fragment which you'd like to drive lifecycle
+     * @param activityClass the activity which will be attached by fragment
+     * @return {@link FragmentController}
+     */
+    public static <F extends Fragment> FragmentController<F> of(
+            F fragment, Class<? extends FragmentActivity> activityClass) {
+        return new FragmentController<>(fragment, activityClass);
+    }
+
+    /**
+     * Generate the {@link FragmentController} for specific fragment, intent and arguments.
+     *
+     * @param fragment  the fragment which you'd like to drive lifecycle
+     * @param intent    the intent which will be retained by activity
+     * @param arguments the arguments which will be retained by fragment
+     * @return {@link FragmentController}
+     */
+    public static <F extends Fragment> FragmentController<F> of(
+            F fragment, Intent intent, Bundle arguments) {
+        return new FragmentController<>(fragment, FragmentControllerActivity.class, intent,
+                arguments);
+    }
+
+    /**
+     * Generate the {@link FragmentController} for specific fragment, activity class and intent.
+     *
+     * @param fragment      the fragment which you'd like to drive lifecycle
+     * @param activityClass the activity which will be attached by fragment
+     * @param intent        the intent which will be retained by activity
+     * @return {@link FragmentController}
+     */
+    public static <F extends Fragment> FragmentController<F> of(
+            F fragment, Class<? extends FragmentActivity> activityClass, Intent intent) {
+        return new FragmentController<>(fragment, activityClass, intent);
+    }
+
+    /**
+     * Generate the {@link FragmentController} for specific fragment, activity class and arguments.
+     *
+     * @param fragment      the fragment which you'd like to drive lifecycle
+     * @param activityClass the activity which will be attached by fragment
+     * @param arguments     the arguments which will be retained by fragment
+     * @return {@link FragmentController}
+     */
+    public static <F extends Fragment> FragmentController<F> of(
+            F fragment, Class<? extends FragmentActivity> activityClass, Bundle arguments) {
+        return new FragmentController<>(fragment, activityClass, arguments);
+    }
+
+    /**
+     * Generate the {@link FragmentController} for specific fragment, activity class, intent and
+     * arguments.
+     *
+     * @param fragment      the fragment which you'd like to drive lifecycle
+     * @param activityClass the activity which will be attached by fragment
+     * @param intent        the intent which will be retained by activity
+     * @param arguments     the arguments which will be retained by fragment
+     * @return {@link FragmentController}
+     */
+    public static <F extends Fragment> FragmentController<F> of(
+            F fragment,
+            Class<? extends FragmentActivity> activityClass,
+            Intent intent,
+            Bundle arguments) {
+        return new FragmentController<>(fragment, activityClass, intent, arguments);
+    }
+
+    /**
+     * Sets up the given fragment by attaching it to an activity, calling its onCreate() through
+     * onResume() lifecycle methods, and then making it visible. Note that the fragment will be
+     * added
+     * to the view with ID 1.
+     */
+    public static <F extends Fragment> F setupFragment(F fragment) {
+        return FragmentController.of(fragment).create().start().resume().visible().get();
+    }
+
+    /**
+     * Sets up the given fragment by attaching it to an activity, calling its onCreate() through
+     * onResume() lifecycle methods, and then making it visible. Note that the fragment will be
+     * added
+     * to the view with ID 1.
+     */
+    public static <F extends Fragment> F setupFragment(
+            F fragment, Class<? extends FragmentActivity> fragmentActivityClass) {
+        return FragmentController.of(fragment, fragmentActivityClass)
+                .create()
+                .start()
+                .resume()
+                .visible()
+                .get();
+    }
+
+    /**
+     * Sets up the given fragment by attaching it to an activity created with the given bundle,
+     * calling its onCreate() through onResume() lifecycle methods, and then making it visible. Note
+     * that the fragment will be added to the view with ID 1.
+     */
+    public static <F extends Fragment> F setupFragment(
+            F fragment, Class<? extends FragmentActivity> fragmentActivityClass, Bundle bundle) {
+        return FragmentController.of(fragment, fragmentActivityClass)
+                .create(bundle)
+                .start()
+                .resume()
+                .visible()
+                .get();
+    }
+
+    /**
+     * Sets up the given fragment by attaching it to an activity created with the given bundle and
+     * container id, calling its onCreate() through onResume() lifecycle methods, and then making it
+     * visible.
+     */
+    public static <F extends Fragment> F setupFragment(
+            F fragment,
+            Class<? extends FragmentActivity> fragmentActivityClass,
+            int containerViewId,
+            Bundle bundle) {
+        return FragmentController.of(fragment, fragmentActivityClass)
+                .create(containerViewId, bundle)
+                .start()
+                .resume()
+                .visible()
+                .get();
+    }
+
+    /**
+     * Creates the activity with {@link Bundle} and adds the fragment to the view with ID {@code
+     * contentViewId}.
+     */
+    public FragmentController<F> create(final int contentViewId, final Bundle bundle) {
+        shadowMainLooper.runPaused(
+                new Runnable() {
+                    @Override
+                    public void run() {
+                        mActivityController
+                                .create(bundle)
+                                .get()
+                                .getSupportFragmentManager()
+                                .beginTransaction()
+                                .add(contentViewId, mFragment)
+                                .commit();
+                    }
+                });
+        return this;
+    }
+
+    /**
+     * Creates the activity with {@link Bundle} and adds the fragment to it. Note that the fragment
+     * will be added to the view with ID 1.
+     */
+    public FragmentController<F> create(final Bundle bundle) {
+        return create(1, bundle);
+    }
+
+    /**
+     * Creates the {@link Fragment} in a newly initialized state and hence will receive a null
+     * savedInstanceState {@link Bundle parameter}
+     */
+    @Override
+    public FragmentController<F> create() {
+        return create(null);
+    }
+
+    /** Drive lifecycle of activity to Start lifetime */
+    public FragmentController<F> start() {
+        shadowMainLooper.runPaused(
+                new Runnable() {
+                    @Override
+                    public void run() {
+                        mActivityController.start();
+                    }
+                });
+        return this;
+    }
+
+    /** Drive lifecycle of activity to Resume lifetime */
+    public FragmentController<F> resume() {
+        shadowMainLooper.runPaused(
+                new Runnable() {
+                    @Override
+                    public void run() {
+                        mActivityController.resume();
+                    }
+                });
+        return this;
+    }
+
+    /** Drive lifecycle of activity to Pause lifetime */
+    public FragmentController<F> pause() {
+        shadowMainLooper.runPaused(
+                new Runnable() {
+                    @Override
+                    public void run() {
+                        mActivityController.pause();
+                    }
+                });
+        return this;
+    }
+
+    /** Drive lifecycle of activity to Stop lifetime */
+    public FragmentController<F> stop() {
+        shadowMainLooper.runPaused(
+                new Runnable() {
+                    @Override
+                    public void run() {
+                        mActivityController.stop();
+                    }
+                });
+        return this;
+    }
+
+    /** Drive lifecycle of activity to Destroy lifetime */
+    @Override
+    public FragmentController<F> destroy() {
+        shadowMainLooper.runPaused(
+                new Runnable() {
+                    @Override
+                    public void run() {
+                        mActivityController.destroy();
+                    }
+                });
+        return this;
+    }
+
+    /** Let activity can be visible lifetime */
+    public FragmentController<F> visible() {
+        shadowMainLooper.runPaused(
+                new Runnable() {
+                    @Override
+                    public void run() {
+                        mActivityController.visible();
+                    }
+                });
+        return this;
+    }
+
+    private static class FragmentControllerActivity extends FragmentActivity {
+
+        @Override
+        protected void onCreate(Bundle savedInstanceState) {
+            super.onCreate(savedInstanceState);
+            LinearLayout view = new LinearLayout(this);
+            view.setId(1);
+
+            setContentView(view);
+        }
+    }
+}
diff --git a/packages/SettingsLib/tests/robotests/fragment/src/main/java/org/robolectric/shadows/androidx/fragment/package-info.java b/packages/SettingsLib/tests/robotests/fragment/src/main/java/org/robolectric/shadows/androidx/fragment/package-info.java
new file mode 100644
index 0000000..dd89441
--- /dev/null
+++ b/packages/SettingsLib/tests/robotests/fragment/src/main/java/org/robolectric/shadows/androidx/fragment/package-info.java
@@ -0,0 +1,23 @@
+/*
+ * 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.
+ */
+
+/**
+ * Testing infrastructure for androidx.fragment library.
+ *
+ * <p>To use this in your project, add the artifact {@code
+ * org.robolectric:shadows-androidx-fragment} to your project.
+ */
+package org.robolectric.shadows.androidx.fragment;
diff --git a/packages/SettingsLib/tests/robotests/fragment/src/test/AndroidManifest.xml b/packages/SettingsLib/tests/robotests/fragment/src/test/AndroidManifest.xml
new file mode 100644
index 0000000..8493c02
--- /dev/null
+++ b/packages/SettingsLib/tests/robotests/fragment/src/test/AndroidManifest.xml
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+          package="org.robolectric.shadows.androidx.fragment">
+
+    <uses-sdk android:targetSdkVersion="28"/>
+</manifest>
diff --git a/packages/SettingsLib/tests/robotests/fragment/src/test/java/org/robolectric/shadows/androidx/fragment/FragmentControllerTest.java b/packages/SettingsLib/tests/robotests/fragment/src/test/java/org/robolectric/shadows/androidx/fragment/FragmentControllerTest.java
new file mode 100644
index 0000000..ef63058
--- /dev/null
+++ b/packages/SettingsLib/tests/robotests/fragment/src/test/java/org/robolectric/shadows/androidx/fragment/FragmentControllerTest.java
@@ -0,0 +1,360 @@
+/*
+ * 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 org.robolectric.shadows.androidx.fragment;
+
+import static android.os.Looper.getMainLooper;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.robolectric.Shadows.shadowOf;
+
+import android.content.Intent;
+import android.os.Bundle;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+import androidx.fragment.app.Fragment;
+import androidx.fragment.app.FragmentActivity;
+
+import org.junit.After;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/** Tests for {@link FragmentController} */
+@RunWith(RobolectricTestRunner.class)
+public class FragmentControllerTest {
+
+    @After
+    public void tearDown() {
+        TranscriptFragment.clearLifecycleEvents();
+    }
+
+    @Test
+    public void initialNotAttached() {
+        final FragmentController<TranscriptFragment> controller =
+                FragmentController.of(new TranscriptFragment());
+
+        assertThat(controller.get().getView()).isNull();
+        assertThat(controller.get().getActivity()).isNull();
+        assertThat(controller.get().isAdded()).isFalse();
+    }
+
+    @Test
+    public void initialNotAttached_customActivity() {
+        final FragmentController<TranscriptFragment> controller =
+                FragmentController.of(new TranscriptFragment(), TestActivity.class);
+
+        assertThat(controller.get().getView()).isNull();
+        assertThat(controller.get().getActivity()).isNull();
+        assertThat(controller.get().isAdded()).isFalse();
+    }
+
+    @Test
+    public void attachedAfterCreate() {
+        final FragmentController<TranscriptFragment> controller =
+                FragmentController.of(new TranscriptFragment());
+
+        controller.create();
+        shadowOf(getMainLooper()).idle();
+
+        assertThat(controller.get().getActivity()).isNotNull();
+        assertThat(controller.get().isAdded()).isTrue();
+        assertThat(controller.get().isResumed()).isFalse();
+    }
+
+    @Test
+    public void attachedAfterCreate_customActivity() {
+        final FragmentController<TranscriptFragment> controller =
+                FragmentController.of(new TranscriptFragment(), TestActivity.class);
+
+        controller.create();
+        shadowOf(getMainLooper()).idle();
+
+        assertThat(controller.get().getActivity()).isNotNull();
+        assertThat(controller.get().getActivity()).isInstanceOf(TestActivity.class);
+        assertThat(controller.get().isAdded()).isTrue();
+        assertThat(controller.get().isResumed()).isFalse();
+    }
+
+    @Test
+    public void attachedAfterCreate_customizedViewId() {
+        final FragmentController<TranscriptFragment> controller =
+                FragmentController.of(new TranscriptFragment(), CustomizedViewIdTestActivity.class);
+
+        controller.create(R.id.custom_activity_view, null).start();
+
+        assertThat(controller.get().getView()).isNotNull();
+        assertThat(controller.get().getActivity()).isNotNull();
+        assertThat(controller.get().isAdded()).isTrue();
+        assertThat(controller.get().isResumed()).isFalse();
+        assertThat((TextView) controller.get().getView().findViewById(R.id.tacos)).isNotNull();
+    }
+
+    @Test
+    public void hasViewAfterStart() {
+        final FragmentController<TranscriptFragment> controller =
+                FragmentController.of(new TranscriptFragment());
+
+        controller.create().start();
+
+        assertThat(controller.get().getView()).isNotNull();
+    }
+
+    @Test
+    public void isResumed() {
+        final FragmentController<TranscriptFragment> controller =
+                FragmentController.of(new TranscriptFragment(), TestActivity.class);
+
+        controller.create().start().resume();
+
+        assertThat(controller.get().getView()).isNotNull();
+        assertThat(controller.get().getActivity()).isNotNull();
+        assertThat(controller.get().isAdded()).isTrue();
+        assertThat(controller.get().isResumed()).isTrue();
+        assertThat((TextView) controller.get().getView().findViewById(R.id.tacos)).isNotNull();
+    }
+
+    @Test
+    public void isPaused() {
+        final FragmentController<TranscriptFragment> controller =
+                FragmentController.of(new TranscriptFragment(), TestActivity.class);
+
+        controller.create().start().resume().pause();
+
+        assertThat(controller.get().getView()).isNotNull();
+        assertThat(controller.get().getActivity()).isNotNull();
+        assertThat(controller.get().isAdded()).isTrue();
+        assertThat(controller.get().isResumed()).isFalse();
+        assertThat(controller.get().getLifecycleEvents())
+                .containsExactly("onCreate", "onStart", "onResume", "onPause")
+                .inOrder();
+    }
+
+    @Test
+    public void isStopped() {
+        final FragmentController<TranscriptFragment> controller =
+                FragmentController.of(new TranscriptFragment(), TestActivity.class);
+
+        controller.create().start().resume().pause().stop();
+
+        assertThat(controller.get().getView()).isNotNull();
+        assertThat(controller.get().getActivity()).isNotNull();
+        assertThat(controller.get().isAdded()).isTrue();
+        assertThat(controller.get().isResumed()).isFalse();
+        assertThat(controller.get().getLifecycleEvents())
+                .containsExactly("onCreate", "onStart", "onResume", "onPause", "onStop")
+                .inOrder();
+    }
+
+    @Test
+    public void withIntent() {
+        final Intent intent = generateTestIntent();
+        final FragmentController<TranscriptFragment> controller =
+                FragmentController.of(new TranscriptFragment(), TestActivity.class, intent);
+
+        controller.create();
+        shadowOf(getMainLooper()).idle();
+        final Intent intentInFragment = controller.get().getActivity().getIntent();
+
+        assertThat(intentInFragment.getAction()).isEqualTo("test_action");
+        assertThat(intentInFragment.getExtras().getString("test_key")).isEqualTo("test_value");
+    }
+
+    @Test
+    public void withArguments() {
+        final Bundle bundle = generateTestBundle();
+        final FragmentController<TranscriptFragment> controller =
+                FragmentController.of(new TranscriptFragment(), TestActivity.class, bundle);
+
+        controller.create();
+        final Bundle args = controller.get().getArguments();
+
+        assertThat(args.getString("test_key")).isEqualTo("test_value");
+    }
+
+    @Test
+    public void withIntentAndArguments() {
+        final Bundle bundle = generateTestBundle();
+        final Intent intent = generateTestIntent();
+        final FragmentController<TranscriptFragment> controller =
+                FragmentController.of(new TranscriptFragment(), TestActivity.class, intent, bundle);
+
+        controller.create();
+        shadowOf(getMainLooper()).idle();
+        final Intent intentInFragment = controller.get().getActivity().getIntent();
+        final Bundle args = controller.get().getArguments();
+
+        assertThat(intentInFragment.getAction()).isEqualTo("test_action");
+        assertThat(intentInFragment.getExtras().getString("test_key")).isEqualTo("test_value");
+        assertThat(args.getString("test_key")).isEqualTo("test_value");
+    }
+
+    @Test
+    public void visible() {
+        final FragmentController<TranscriptFragment> controller =
+                FragmentController.of(new TranscriptFragment(), TestActivity.class);
+
+        controller.create().start().resume();
+
+        assertThat(controller.get().isVisible()).isFalse();
+
+        controller.visible();
+
+        assertThat(controller.get().isVisible()).isTrue();
+    }
+
+    @Test
+    public void setupFragmentWithFragment_fragmentHasCorrectLifecycle() {
+        TranscriptFragment fragment = FragmentController.setupFragment(new TranscriptFragment());
+
+        assertThat(fragment.getLifecycleEvents())
+                .containsExactly("onCreate", "onStart", "onResume")
+                .inOrder();
+        assertThat(fragment.isVisible()).isTrue();
+    }
+
+    @Test
+    public void setupFragmentWithFragmentAndActivity_fragmentHasCorrectLifecycle() {
+        TranscriptFragment fragment =
+                FragmentController.setupFragment(new TranscriptFragment(), TestActivity.class);
+
+        assertThat(fragment.getLifecycleEvents())
+                .containsExactly("onCreate", "onStart", "onResume")
+                .inOrder();
+        assertThat(fragment.isVisible()).isTrue();
+    }
+
+    @Test
+    public void setupFragmentWithFragmentAndActivityAndBundle_HasCorrectLifecycle() {
+        Bundle testBundle = generateTestBundle();
+        TranscriptFragment fragment =
+                FragmentController.setupFragment(new TranscriptFragment(), TestActivity.class,
+                        testBundle);
+
+        assertThat(fragment.getLifecycleEvents())
+                .containsExactly("onCreate", "onStart", "onResume")
+                .inOrder();
+        assertThat(fragment.isVisible()).isTrue();
+    }
+
+    @Test
+    public void
+            setupFragmentWithFragment_Activity_ContainViewIdAndBundle_HasCorrectLifecycle() {
+        Bundle testBundle = generateTestBundle();
+        TranscriptFragment fragment =
+                FragmentController.setupFragment(
+                        new TranscriptFragment(),
+                        CustomizedViewIdTestActivity.class,
+                        R.id.custom_activity_view,
+                        testBundle);
+
+        assertThat(fragment.getLifecycleEvents())
+                .containsExactly("onCreate", "onStart", "onResume")
+                .inOrder();
+        assertThat(fragment.isVisible()).isTrue();
+    }
+
+    private Intent generateTestIntent() {
+        final Intent testIntent = new Intent("test_action").putExtra("test_key", "test_value");
+        return testIntent;
+    }
+
+    private Bundle generateTestBundle() {
+        final Bundle testBundle = new Bundle();
+        testBundle.putString("test_key", "test_value");
+
+        return testBundle;
+    }
+
+    /** A Fragment which can record lifecycle status for test. */
+    public static class TranscriptFragment extends Fragment {
+
+        public static final List<String> sLifecycleEvents = new ArrayList<>();
+
+        @Override
+        public void onCreate(Bundle savedInstanceState) {
+            super.onCreate(savedInstanceState);
+            sLifecycleEvents.add("onCreate");
+        }
+
+        @Override
+        public void onStart() {
+            super.onStart();
+            sLifecycleEvents.add("onStart");
+        }
+
+        @Override
+        public void onResume() {
+            super.onResume();
+            sLifecycleEvents.add("onResume");
+        }
+
+        @Override
+        public void onPause() {
+            super.onPause();
+            sLifecycleEvents.add("onPause");
+        }
+
+        @Override
+        public void onStop() {
+            super.onStop();
+            sLifecycleEvents.add("onStop");
+        }
+
+        @Override
+        public View onCreateView(
+                LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
+            return inflater.inflate(R.layout.fragment_contents, container, false);
+        }
+
+        public List<String> getLifecycleEvents() {
+            return sLifecycleEvents;
+        }
+
+        public static void clearLifecycleEvents() {
+            sLifecycleEvents.clear();
+        }
+    }
+
+    /** A Activity which set a default view for test. */
+    public static class TestActivity extends FragmentActivity {
+        @Override
+        protected void onCreate(Bundle savedInstanceState) {
+            super.onCreate(savedInstanceState);
+            LinearLayout view = new LinearLayout(this);
+            view.setId(1);
+
+            setContentView(view);
+        }
+    }
+
+    /** A Activity which has a custom view for test. */
+    public static class CustomizedViewIdTestActivity extends FragmentActivity {
+        @Override
+        protected void onCreate(Bundle savedInstanceState) {
+            super.onCreate(savedInstanceState);
+            setContentView(R.layout.custom_activity_view);
+        }
+    }
+}
diff --git a/packages/SettingsLib/tests/robotests/fragment/src/test/resources/res/layout/custom_activity_view.xml b/packages/SettingsLib/tests/robotests/fragment/src/test/resources/res/layout/custom_activity_view.xml
new file mode 100644
index 0000000..c074f30
--- /dev/null
+++ b/packages/SettingsLib/tests/robotests/fragment/src/test/resources/res/layout/custom_activity_view.xml
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/custom_activity_view"
+    android:layout_width="fill_parent"
+    android:layout_height="fill_parent"
+    android:orientation="vertical">
+
+</LinearLayout>
diff --git a/packages/SettingsLib/tests/robotests/fragment/src/test/resources/res/layout/fragment_contents.xml b/packages/SettingsLib/tests/robotests/fragment/src/test/resources/res/layout/fragment_contents.xml
new file mode 100644
index 0000000..425b2bb
--- /dev/null
+++ b/packages/SettingsLib/tests/robotests/fragment/src/test/resources/res/layout/fragment_contents.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="fill_parent"
+    android:layout_height="fill_parent"
+    android:orientation="vertical">
+
+  <TextView
+      android:id="@+id/tacos"
+      android:layout_width="wrap_content"
+      android:layout_height="wrap_content"
+      android:text="TACOS"/>
+
+  <TextView
+      android:id="@+id/burritos"
+      android:layout_width="wrap_content"
+      android:layout_height="wrap_content"
+      android:text="BURRITOS"/>
+
+</LinearLayout>
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/UtilsTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/UtilsTest.java
index 4a913c8..bb72375 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/UtilsTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/UtilsTest.java
@@ -25,7 +25,6 @@
 import static org.mockito.Mockito.when;
 
 import android.app.ActivityManager;
-import android.content.ContentResolver;
 import android.content.Context;
 import android.content.Intent;
 import android.content.res.Resources;
@@ -58,12 +57,10 @@
 import org.robolectric.shadows.ShadowSettings;
 
 import java.util.ArrayList;
-import java.util.HashMap;
 import java.util.List;
-import java.util.Map;
 
 @RunWith(RobolectricTestRunner.class)
-@Config(shadows = {UtilsTest.ShadowSecure.class, UtilsTest.ShadowLocationManager.class})
+@Config(shadows = {UtilsTest.ShadowLocationManager.class})
 public class UtilsTest {
     private static final double[] TEST_PERCENTAGES = {0, 0.4, 0.5, 0.6, 49, 49.3, 49.8, 50, 100};
     private static final String TAG = "UtilsTest";
@@ -94,7 +91,7 @@
         mContext = spy(RuntimeEnvironment.application);
         when(mContext.getSystemService(Context.LOCATION_SERVICE)).thenReturn(mLocationManager);
         when(mContext.getSystemService(UsbManager.class)).thenReturn(mUsbManager);
-        ShadowSecure.reset();
+        ShadowSettings.ShadowSecure.reset();
         mAudioManager = mContext.getSystemService(AudioManager.class);
     }
 
@@ -111,15 +108,16 @@
                 Settings.Secure.LOCATION_CHANGER_QUICK_SETTINGS);
 
         assertThat(Settings.Secure.getInt(mContext.getContentResolver(),
-                Settings.Secure.LOCATION_CHANGER, Settings.Secure.LOCATION_CHANGER_UNKNOWN))
-                .isEqualTo(Settings.Secure.LOCATION_CHANGER_QUICK_SETTINGS);
+                Settings.Secure.LOCATION_CHANGER,
+                Settings.Secure.LOCATION_CHANGER_UNKNOWN)).isEqualTo(
+                Settings.Secure.LOCATION_CHANGER_QUICK_SETTINGS);
     }
 
     @Test
     public void testFormatPercentage_RoundTrue_RoundUpIfPossible() {
-        final String[] expectedPercentages = {PERCENTAGE_0, PERCENTAGE_0, PERCENTAGE_1,
-                PERCENTAGE_1, PERCENTAGE_49, PERCENTAGE_49, PERCENTAGE_50, PERCENTAGE_50,
-                PERCENTAGE_100};
+        final String[] expectedPercentages =
+                {PERCENTAGE_0, PERCENTAGE_0, PERCENTAGE_1, PERCENTAGE_1, PERCENTAGE_49,
+                        PERCENTAGE_49, PERCENTAGE_50, PERCENTAGE_50, PERCENTAGE_100};
 
         for (int i = 0, size = TEST_PERCENTAGES.length; i < size; i++) {
             final String percentage = Utils.formatPercentage(TEST_PERCENTAGES[i], true);
@@ -129,9 +127,9 @@
 
     @Test
     public void testFormatPercentage_RoundFalse_NoRound() {
-        final String[] expectedPercentages = {PERCENTAGE_0, PERCENTAGE_0, PERCENTAGE_0,
-                PERCENTAGE_0, PERCENTAGE_49, PERCENTAGE_49, PERCENTAGE_49, PERCENTAGE_50,
-                PERCENTAGE_100};
+        final String[] expectedPercentages =
+                {PERCENTAGE_0, PERCENTAGE_0, PERCENTAGE_0, PERCENTAGE_0, PERCENTAGE_49,
+                        PERCENTAGE_49, PERCENTAGE_49, PERCENTAGE_50, PERCENTAGE_100};
 
         for (int i = 0, size = TEST_PERCENTAGES.length; i < size; i++) {
             final String percentage = Utils.formatPercentage(TEST_PERCENTAGES[i], false);
@@ -143,12 +141,7 @@
     public void testGetDefaultStorageManagerDaysToRetain_storageManagerDaysToRetainUsesResources() {
         Resources resources = mock(Resources.class);
         when(resources.getInteger(
-                eq(
-                        com.android
-                                .internal
-                                .R
-                                .integer
-                                .config_storageManagerDaystoRetainDefault)))
+                eq(com.android.internal.R.integer.config_storageManagerDaystoRetainDefault)))
                 .thenReturn(60);
         assertThat(Utils.getDefaultStorageManagerDaysToRetain(resources)).isEqualTo(60);
     }
@@ -163,31 +156,6 @@
         return intent -> TextUtils.equals(expected, intent.getAction());
     }
 
-    @Implements(value = Settings.Secure.class)
-    public static class ShadowSecure extends ShadowSettings.ShadowSecure {
-        private static Map<String, Integer> map = new HashMap<>();
-
-        @Implementation
-        public static boolean putIntForUser(ContentResolver cr, String name, int value,
-                int userHandle) {
-            map.put(name, value);
-            return true;
-        }
-
-        @Implementation
-        public static int getIntForUser(ContentResolver cr, String name, int def, int userHandle) {
-            if (map.containsKey(name)) {
-                return map.get(name);
-            } else {
-                return def;
-            }
-        }
-
-        public static void reset() {
-            map.clear();
-        }
-    }
-
     @Implements(value = LocationManager.class)
     public static class ShadowLocationManager {
 
@@ -337,9 +305,8 @@
 
     @Test
     public void getBatteryStatus_statusIsFull_returnFullString() {
-        final Intent intent = new Intent()
-                .putExtra(BatteryManager.EXTRA_LEVEL, 100)
-                .putExtra(BatteryManager.EXTRA_SCALE, 100);
+        final Intent intent = new Intent().putExtra(BatteryManager.EXTRA_LEVEL, 100).putExtra(
+                BatteryManager.EXTRA_SCALE, 100);
         final Resources resources = mContext.getResources();
 
         assertThat(Utils.getBatteryStatus(mContext, intent, /* compactStatus= */ false)).isEqualTo(
@@ -348,9 +315,8 @@
 
     @Test
     public void getBatteryStatus_statusIsFullAndUseCompactStatus_returnFullyChargedString() {
-        final Intent intent = new Intent()
-                .putExtra(BatteryManager.EXTRA_LEVEL, 100)
-                .putExtra(BatteryManager.EXTRA_SCALE, 100);
+        final Intent intent = new Intent().putExtra(BatteryManager.EXTRA_LEVEL, 100).putExtra(
+                BatteryManager.EXTRA_SCALE, 100);
         final Resources resources = mContext.getResources();
 
         assertThat(Utils.getBatteryStatus(mContext, intent, /* compactStatus= */ true)).isEqualTo(
@@ -516,7 +482,6 @@
         when(mUsbPort.getStatus()).thenReturn(mUsbPortStatus);
         when(mUsbPort.supportsComplianceWarnings()).thenReturn(true);
         when(mUsbPortStatus.isConnected()).thenReturn(true);
-        when(mUsbPortStatus.getComplianceWarnings())
-                .thenReturn(new int[]{complianceWarningType});
+        when(mUsbPortStatus.getComplianceWarnings()).thenReturn(new int[]{complianceWarningType});
     }
 }
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/accessibility/AccessibilityUtilsTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/accessibility/AccessibilityUtilsTest.java
index 44fdaec..3de8446 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/accessibility/AccessibilityUtilsTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/accessibility/AccessibilityUtilsTest.java
@@ -23,13 +23,17 @@
 import android.os.UserHandle;
 import android.provider.Settings;
 
+import com.android.settingslib.testutils.shadow.ShadowSecure;
+
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.robolectric.RobolectricTestRunner;
 import org.robolectric.RuntimeEnvironment;
+import org.robolectric.annotation.Config;
 
 @RunWith(RobolectricTestRunner.class)
+@Config(shadows = {ShadowSecure.class})
 public class AccessibilityUtilsTest {
 
     private Context mContext;
@@ -46,7 +50,7 @@
 
     @Test
     public void getEnabledServicesFromSettings_badFormat_emptyResult() {
-        Settings.Secure.putStringForUser(
+        ShadowSecure.putStringForUser(
                 mContext.getContentResolver(), Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES,
                 ":",
                 UserHandle.myUserId());
@@ -57,7 +61,7 @@
     @Test
     public void getEnabledServicesFromSettings_1Service_1result() {
         final ComponentName cn = new ComponentName("pkg", "serv");
-        Settings.Secure.putStringForUser(
+        ShadowSecure.putStringForUser(
                 mContext.getContentResolver(), Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES,
                 cn.flattenToString() + ":",
                 UserHandle.myUserId());
@@ -70,7 +74,7 @@
     public void getEnabledServicesFromSettings_2Services_2results() {
         final ComponentName cn1 = new ComponentName("pkg", "serv");
         final ComponentName cn2 = new ComponentName("pkg", "serv2");
-        Settings.Secure.putStringForUser(
+        ShadowSecure.putStringForUser(
                 mContext.getContentResolver(), Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES,
                 cn1.flattenToString() + ":" + cn2.flattenToString(),
                 UserHandle.myUserId());
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/applications/RecentAppOpsAccessesTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/applications/RecentAppOpsAccessesTest.java
index cb62a73..f9505dd 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/applications/RecentAppOpsAccessesTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/applications/RecentAppOpsAccessesTest.java
@@ -37,6 +37,8 @@
 import android.os.UserManager;
 import android.util.LongSparseArray;
 
+import com.android.settingslib.testutils.shadow.ShadowPermissionChecker;
+
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -45,7 +47,6 @@
 import org.robolectric.RobolectricTestRunner;
 import org.robolectric.RuntimeEnvironment;
 import org.robolectric.annotation.Config;
-import org.robolectric.shadows.ShadowPermissionChecker;
 
 import java.time.Clock;
 import java.util.ArrayList;
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceTest.java
index 6444f3b..4b61ff1 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceTest.java
@@ -1015,6 +1015,13 @@
     }
 
     @Test
+    public void setName_setDeviceNameIsEmpty() {
+        mCachedDevice.setName("");
+
+        verify(mDevice, never()).setAlias(any());
+    }
+
+    @Test
     public void getProfileConnectionState_nullProfile_returnDisconnected() {
         assertThat(mCachedDevice.getProfileConnectionState(null)).isEqualTo(
                 BluetoothProfile.STATE_DISCONNECTED);
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/core/instrumentation/MetricsFeatureProviderTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/core/instrumentation/MetricsFeatureProviderTest.java
index dd8d54a..a2e8c59 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/core/instrumentation/MetricsFeatureProviderTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/core/instrumentation/MetricsFeatureProviderTest.java
@@ -38,6 +38,7 @@
 import org.robolectric.Robolectric;
 import org.robolectric.RobolectricTestRunner;
 import org.robolectric.RuntimeEnvironment;
+import org.robolectric.annotation.LooperMode;
 import org.robolectric.util.ReflectionHelpers;
 
 import java.util.ArrayList;
@@ -167,6 +168,7 @@
     }
 
     @Test
+    @LooperMode(LooperMode.Mode.PAUSED)
     public void getAttribution_notSet_shouldReturnUnknown() {
         final Activity activity = Robolectric.setupActivity(Activity.class);
 
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/core/instrumentation/SettingsJankMonitorTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/core/instrumentation/SettingsJankMonitorTest.java
index d67d44b..25833b3 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/core/instrumentation/SettingsJankMonitorTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/core/instrumentation/SettingsJankMonitorTest.java
@@ -36,6 +36,7 @@
 
 import com.android.internal.jank.InteractionJankMonitor;
 import com.android.internal.jank.InteractionJankMonitor.CujType;
+import com.android.settingslib.testutils.OverpoweredReflectionHelper;
 import com.android.settingslib.testutils.shadow.ShadowInteractionJankMonitor;
 
 import org.junit.Before;
@@ -51,7 +52,6 @@
 import org.robolectric.annotation.Implementation;
 import org.robolectric.annotation.Implements;
 import org.robolectric.annotation.Resetter;
-import org.robolectric.util.ReflectionHelpers;
 
 import java.util.concurrent.ScheduledExecutorService;
 import java.util.concurrent.TimeUnit;
@@ -83,8 +83,10 @@
     public void setUp() {
         ShadowInteractionJankMonitor.reset();
         when(ShadowInteractionJankMonitor.MOCK_INSTANCE.begin(any())).thenReturn(true);
-        ReflectionHelpers.setStaticField(SettingsJankMonitor.class, "scheduledExecutorService",
-                mScheduledExecutorService);
+        OverpoweredReflectionHelper
+                .setStaticField(SettingsJankMonitor.class,
+                        "scheduledExecutorService",
+                        mScheduledExecutorService);
     }
 
     @Test
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/core/lifecycle/HideNonSystemOverlayMixinTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/core/lifecycle/HideNonSystemOverlayMixinTest.java
index cf702b53..471dac0 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/core/lifecycle/HideNonSystemOverlayMixinTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/core/lifecycle/HideNonSystemOverlayMixinTest.java
@@ -37,8 +37,10 @@
 import org.robolectric.Robolectric;
 import org.robolectric.RobolectricTestRunner;
 import org.robolectric.android.controller.ActivityController;
+import org.robolectric.annotation.LooperMode;
 
 @RunWith(RobolectricTestRunner.class)
+@LooperMode(LooperMode.Mode.PAUSED)
 public class HideNonSystemOverlayMixinTest {
 
     private ActivityController<TestActivity> mActivityController;
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/development/DevelopmentSettingsEnablerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/development/DevelopmentSettingsEnablerTest.java
index 3475ff7..b009abd 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/development/DevelopmentSettingsEnablerTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/development/DevelopmentSettingsEnablerTest.java
@@ -22,13 +22,14 @@
 import android.os.UserManager;
 import android.provider.Settings;
 
+import com.android.settingslib.testutils.shadow.ShadowUserManager;
+
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.robolectric.RobolectricTestRunner;
 import org.robolectric.RuntimeEnvironment;
 import org.robolectric.shadow.api.Shadow;
-import org.robolectric.shadows.ShadowUserManager;
 
 @RunWith(RobolectricTestRunner.class)
 public class DevelopmentSettingsEnablerTest {
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/license/LicenseHtmlGeneratorFromXmlTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/license/LicenseHtmlGeneratorFromXmlTest.java
index 8e33ca3..0cabab2 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/license/LicenseHtmlGeneratorFromXmlTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/license/LicenseHtmlGeneratorFromXmlTest.java
@@ -21,6 +21,7 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.robolectric.RobolectricTestRunner;
+import org.robolectric.annotation.LooperMode;
 import org.xmlpull.v1.XmlPullParserException;
 
 import java.io.ByteArrayInputStream;
@@ -269,6 +270,7 @@
     }
 
     @Test
+    @LooperMode(LooperMode.Mode.PAUSED)
     public void testGenerateHtmlWithCustomHeading() throws Exception {
         List<File> xmlFiles = new ArrayList<>();
         Map<String, Map<String, Set<String>>> fileNameToLibraryToContentIdMap = new HashMap<>();
@@ -292,6 +294,7 @@
     }
 
     @Test
+    @LooperMode(LooperMode.Mode.PAUSED)
     public void testGenerateNewHtmlWithCustomHeading() throws Exception {
         List<File> xmlFiles = new ArrayList<>();
         Map<String, Map<String, Set<String>>> fileNameToLibraryToContentIdMap = new HashMap<>();
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/MediaDeviceTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/MediaDeviceTest.java
index c058a61..f22e090 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/MediaDeviceTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/MediaDeviceTest.java
@@ -19,6 +19,9 @@
 import static android.media.MediaRoute2Info.TYPE_BUILTIN_SPEAKER;
 import static android.media.MediaRoute2Info.TYPE_REMOTE_SPEAKER;
 import static android.media.MediaRoute2Info.TYPE_WIRED_HEADPHONES;
+import static android.media.RouteListingPreference.Item.SELECTION_BEHAVIOR_GO_TO_APP;
+
+import static com.android.settingslib.media.MediaDevice.SelectionBehavior.SELECTION_BEHAVIOR_TRANSFER;
 
 import static com.google.common.truth.Truth.assertThat;
 
@@ -32,6 +35,7 @@
 import android.media.MediaRoute2Info;
 import android.media.MediaRouter2Manager;
 import android.media.NearbyDevice;
+import android.media.RouteListingPreference;
 import android.os.Parcel;
 
 import com.android.settingslib.bluetooth.A2dpProfile;
@@ -110,6 +114,8 @@
     @Mock
     private MediaRouter2Manager mMediaRouter2Manager;
 
+    private RouteListingPreference.Item mItem;
+
     private BluetoothMediaDevice mBluetoothMediaDevice1;
     private BluetoothMediaDevice mBluetoothMediaDevice2;
     private BluetoothMediaDevice mBluetoothMediaDevice3;
@@ -497,4 +503,21 @@
 
         assertThat(mBluetoothMediaDevice1.getFeatures().size()).isEqualTo(0);
     }
+
+    @Test
+    public void getSelectionBehavior_setItemWithSelectionBehaviorOnSystemRoute_returnTransfer() {
+        mItem = new RouteListingPreference.Item.Builder(DEVICE_ADDRESS_1)
+                .setSelectionBehavior(SELECTION_BEHAVIOR_GO_TO_APP)
+                .build();
+        mBluetoothMediaDevice1 = new BluetoothMediaDevice(mContext, mCachedDevice1,
+                mMediaRouter2Manager, null /* MediaRoute2Info */, TEST_PACKAGE_NAME, mItem);
+        mPhoneMediaDevice =
+                new PhoneMediaDevice(mContext, mMediaRouter2Manager, mPhoneRouteInfo,
+                        TEST_PACKAGE_NAME, mItem);
+
+        assertThat(mBluetoothMediaDevice1.getSelectionBehavior()).isEqualTo(
+                SELECTION_BEHAVIOR_TRANSFER);
+        assertThat(mPhoneMediaDevice.getSelectionBehavior()).isEqualTo(
+                SELECTION_BEHAVIOR_TRANSFER);
+    }
 }
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/package-info.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/package-info.java
new file mode 100644
index 0000000..9e9725f
--- /dev/null
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * 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.
+ */
+
+@LooperMode(LooperMode.Mode.LEGACY)
+package com.android.settingslib;
+
+import org.robolectric.annotation.LooperMode;
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/AnimatedImageViewTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/AnimatedImageViewTest.java
index d41d511..faec02f7 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/AnimatedImageViewTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/AnimatedImageViewTest.java
@@ -27,6 +27,7 @@
 import org.junit.runner.RunWith;
 import org.robolectric.Robolectric;
 import org.robolectric.RobolectricTestRunner;
+import org.robolectric.annotation.LooperMode;
 
 @RunWith(RobolectricTestRunner.class)
 public class AnimatedImageViewTest {
@@ -40,6 +41,7 @@
     }
 
     @Test
+    @LooperMode(LooperMode.Mode.PAUSED)
     public void testAnimation_ViewVisible_AnimationRunning() {
         mAnimatedImageView.setVisibility(View.VISIBLE);
         mAnimatedImageView.setAnimating(true);
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/BannerMessagePreferenceTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/BannerMessagePreferenceTest.java
index 0a48f19..0d88913 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/BannerMessagePreferenceTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/BannerMessagePreferenceTest.java
@@ -41,6 +41,8 @@
 import androidx.preference.PreferenceViewHolder;
 import androidx.preference.R;
 
+import com.android.settingslib.testutils.OverpoweredReflectionHelper;
+
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -502,14 +504,18 @@
     private void assumeAndroidR() {
         ReflectionHelpers.setStaticField(Build.VERSION.class, "SDK_INT", 30);
         ReflectionHelpers.setStaticField(Build.VERSION.class, "CODENAME", "R");
-        ReflectionHelpers.setStaticField(BannerMessagePreference.class, "IS_AT_LEAST_S", false);
+        OverpoweredReflectionHelper
+                .setStaticField(BannerMessagePreference.class, "IS_AT_LEAST_S", false);
         // Reset view holder to use correct layout.
     }
 
+
+
     private void assumeAndroidS() {
         ReflectionHelpers.setStaticField(Build.VERSION.class, "SDK_INT", 31);
         ReflectionHelpers.setStaticField(Build.VERSION.class, "CODENAME", "S");
-        ReflectionHelpers.setStaticField(BannerMessagePreference.class, "IS_AT_LEAST_S", true);
+        OverpoweredReflectionHelper
+                .setStaticField(BannerMessagePreference.class, "IS_AT_LEAST_S", true);
         // Re-inflate view to update layout.
         setUpViewHolder();
     }
diff --git a/packages/SettingsLib/tests/robotests/testutils/com/android/settingslib/testutils/OverpoweredReflectionHelper.java b/packages/SettingsLib/tests/robotests/testutils/com/android/settingslib/testutils/OverpoweredReflectionHelper.java
new file mode 100644
index 0000000..4fcc5a1
--- /dev/null
+++ b/packages/SettingsLib/tests/robotests/testutils/com/android/settingslib/testutils/OverpoweredReflectionHelper.java
@@ -0,0 +1,99 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.testutils;
+
+import org.robolectric.util.ReflectionHelpers;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+
+public class OverpoweredReflectionHelper extends ReflectionHelpers {
+
+    /**
+     * Robolectric upstream does not rely on or encourage this behaviour.
+     *
+     * @param field
+     */
+    private static void makeFieldVeryAccessible(Field field) {
+        field.setAccessible(true);
+        // remove 'final' modifier if present
+        if ((field.getModifiers() & Modifier.FINAL) == Modifier.FINAL) {
+            Field modifiersField = getModifiersField();
+            modifiersField.setAccessible(true);
+            try {
+                modifiersField.setInt(field, field.getModifiers() & ~Modifier.FINAL);
+            } catch (IllegalAccessException e) {
+
+                throw new AssertionError(e);
+            }
+        }
+    }
+
+    private static Field getModifiersField() {
+        try {
+            return Field.class.getDeclaredField("modifiers");
+        } catch (NoSuchFieldException e) {
+            try {
+                Method getFieldsMethod =
+                        Class.class.getDeclaredMethod("getDeclaredFields0", boolean.class);
+                getFieldsMethod.setAccessible(true);
+                Field[] fields = (Field[]) getFieldsMethod.invoke(Field.class, false);
+                for (Field modifiersField : fields) {
+                    if ("modifiers".equals(modifiersField.getName())) {
+                        return modifiersField;
+                    }
+                }
+            } catch (ReflectiveOperationException innerE) {
+                throw new AssertionError(innerE);
+            }
+        }
+        throw new AssertionError();
+    }
+
+    /**
+     * Reflectively set the value of a static field.
+     *
+     * @param field Field object.
+     * @param fieldNewValue The new value.
+     */
+    public static void setStaticField(Field field, Object fieldNewValue) {
+        try {
+            makeFieldVeryAccessible(field);
+            field.setAccessible(true);
+            field.set(null, fieldNewValue);
+        } catch (Exception e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    /**
+     * Reflectively set the value of a static field.
+     *
+     * @param clazz Target class.
+     * @param fieldName The field name.
+     * @param fieldNewValue The new value.
+     */
+    public static void setStaticField(Class<?> clazz, String fieldName, Object fieldNewValue) {
+        try {
+            setStaticField(clazz.getDeclaredField(fieldName), fieldNewValue);
+        } catch (Exception e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+}
diff --git a/packages/SettingsLib/tests/robotests/testutils/com/android/settingslib/testutils/shadow/ShadowActivityManager.java b/packages/SettingsLib/tests/robotests/testutils/com/android/settingslib/testutils/shadow/ShadowActivityManager.java
index 924eb04..0b9ba8d 100644
--- a/packages/SettingsLib/tests/robotests/testutils/com/android/settingslib/testutils/shadow/ShadowActivityManager.java
+++ b/packages/SettingsLib/tests/robotests/testutils/com/android/settingslib/testutils/shadow/ShadowActivityManager.java
@@ -16,23 +16,27 @@
 
 package com.android.settingslib.testutils.shadow;
 
+import static android.os.Build.VERSION_CODES.O;
+
 import android.app.ActivityManager;
+import android.app.IActivityManager;
 
 import org.robolectric.RuntimeEnvironment;
 import org.robolectric.annotation.Implementation;
 import org.robolectric.annotation.Implements;
 import org.robolectric.annotation.Resetter;
 import org.robolectric.shadow.api.Shadow;
+import org.robolectric.util.ReflectionHelpers;
 
 @Implements(ActivityManager.class)
 public class ShadowActivityManager {
     private static int sCurrentUserId = 0;
-    private int mUserSwitchedTo = -1;
+    private static int sUserSwitchedTo = -1;
 
     @Resetter
-    public void reset() {
+    public static void reset() {
         sCurrentUserId = 0;
-        mUserSwitchedTo = 0;
+        sUserSwitchedTo = 0;
     }
 
     @Implementation
@@ -42,16 +46,21 @@
 
     @Implementation
     protected boolean switchUser(int userId) {
-        mUserSwitchedTo = userId;
+        sUserSwitchedTo = userId;
         return true;
     }
 
+    @Implementation(minSdk = O)
+    protected static IActivityManager getService() {
+        return ReflectionHelpers.createNullProxy(IActivityManager.class);
+    }
+
     public boolean getSwitchUserCalled() {
-        return mUserSwitchedTo != -1;
+        return sUserSwitchedTo != -1;
     }
 
     public int getUserSwitchedTo() {
-        return mUserSwitchedTo;
+        return sUserSwitchedTo;
     }
 
     public static void setCurrentUser(int userId) {
diff --git a/packages/SettingsLib/tests/robotests/testutils/com/android/settingslib/testutils/shadow/ShadowDefaultDialerManager.java b/packages/SettingsLib/tests/robotests/testutils/com/android/settingslib/testutils/shadow/ShadowDefaultDialerManager.java
index 2c0792f..bbfdb7f 100644
--- a/packages/SettingsLib/tests/robotests/testutils/com/android/settingslib/testutils/shadow/ShadowDefaultDialerManager.java
+++ b/packages/SettingsLib/tests/robotests/testutils/com/android/settingslib/testutils/shadow/ShadowDefaultDialerManager.java
@@ -29,7 +29,7 @@
     private static String sDefaultDialer;
 
     @Resetter
-    public void reset() {
+    public static void reset() {
         sDefaultDialer = null;
     }
 
diff --git a/packages/SettingsLib/tests/robotests/testutils/com/android/settingslib/testutils/shadow/ShadowPermissionChecker.java b/packages/SettingsLib/tests/robotests/testutils/com/android/settingslib/testutils/shadow/ShadowPermissionChecker.java
new file mode 100644
index 0000000..fae3aea
--- /dev/null
+++ b/packages/SettingsLib/tests/robotests/testutils/com/android/settingslib/testutils/shadow/ShadowPermissionChecker.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.testutils.shadow;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.AttributionSource;
+import android.content.Context;
+import android.content.PermissionChecker;
+
+import org.robolectric.annotation.Implementation;
+import org.robolectric.annotation.Implements;
+
+import java.util.HashMap;
+import java.util.Map;
+/** Shadow class of {@link PermissionChecker}. */
+@Implements(PermissionChecker.class)
+public class ShadowPermissionChecker {
+    private static final Map<String, Map<String, Integer>> RESULTS = new HashMap<>();
+    /** Set the result of permission check for a specific permission. */
+    public static void setResult(String packageName, String permission, int result) {
+        if (!RESULTS.containsKey(packageName)) {
+            RESULTS.put(packageName, new HashMap<>());
+        }
+        RESULTS.get(packageName).put(permission, result);
+    }
+    /** Check the permission of calling package. */
+    @Implementation
+    public static int checkCallingPermissionForDataDelivery(
+            Context context,
+            String permission,
+            String packageName,
+            String attributionTag,
+            String message) {
+        return RESULTS.containsKey(packageName) && RESULTS.get(packageName).containsKey(permission)
+                ? RESULTS.get(packageName).get(permission)
+                : PermissionChecker.checkCallingPermissionForDataDelivery(
+                        context, permission, packageName, attributionTag, message);
+    }
+    /** Check general permission. */
+    @Implementation
+    public static int checkPermissionForDataDelivery(
+            Context context,
+            String permission,
+            int pid,
+            int uid,
+            String packageName,
+            String attributionTag,
+            String message) {
+        return RESULTS.containsKey(packageName) && RESULTS.get(packageName).containsKey(permission)
+                ? RESULTS.get(packageName).get(permission)
+                : PermissionChecker.checkPermissionForDataDelivery(
+                        context, permission, pid, uid, packageName, attributionTag, message);
+    }
+    /** Check general permission. */
+    @Implementation
+    public static int checkPermissionForPreflight(@NonNull Context context,
+            @NonNull String permission, int pid, int uid, @Nullable String packageName) {
+        return checkPermissionForPreflight(context, permission, new AttributionSource(
+                uid, packageName, null /*attributionTag*/));
+    }
+    /** Check general permission. */
+    @Implementation
+    public static int checkPermissionForPreflight(@NonNull Context context,
+            @NonNull String permission, @NonNull AttributionSource attributionSource) {
+        final String packageName = attributionSource.getPackageName();
+        return RESULTS.containsKey(packageName) && RESULTS.get(packageName).containsKey(permission)
+                ? RESULTS.get(packageName).get(permission)
+                : PermissionChecker.checkPermissionForPreflight(
+                        context, permission, attributionSource);
+    }
+}
diff --git a/packages/SettingsLib/tests/robotests/testutils/com/android/settingslib/testutils/shadow/ShadowSecure.java b/packages/SettingsLib/tests/robotests/testutils/com/android/settingslib/testutils/shadow/ShadowSecure.java
new file mode 100644
index 0000000..70ebc67
--- /dev/null
+++ b/packages/SettingsLib/tests/robotests/testutils/com/android/settingslib/testutils/shadow/ShadowSecure.java
@@ -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.settingslib.testutils.shadow;
+
+import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR1;
+
+import android.content.ContentResolver;
+import android.provider.Settings;
+
+import org.robolectric.annotation.Implementation;
+import org.robolectric.annotation.Implements;
+import org.robolectric.shadows.ShadowSettings;
+
+@Implements(value = Settings.Secure.class)
+public class ShadowSecure extends ShadowSettings.ShadowSecure {
+    @Implementation(minSdk = JELLY_BEAN_MR1)
+    public static boolean putStringForUser(ContentResolver cr, String name, String value,
+            int userHandle) {
+        return putString(cr, name, value);
+    }
+}
diff --git a/packages/SettingsLib/tests/robotests/testutils/com/android/settingslib/testutils/shadow/ShadowSmsApplication.java b/packages/SettingsLib/tests/robotests/testutils/com/android/settingslib/testutils/shadow/ShadowSmsApplication.java
index 381d072..5ac0a87 100644
--- a/packages/SettingsLib/tests/robotests/testutils/com/android/settingslib/testutils/shadow/ShadowSmsApplication.java
+++ b/packages/SettingsLib/tests/robotests/testutils/com/android/settingslib/testutils/shadow/ShadowSmsApplication.java
@@ -31,7 +31,7 @@
     private static ComponentName sDefaultSmsApplication;
 
     @Resetter
-    public void reset() {
+    public static void reset() {
         sDefaultSmsApplication = null;
     }
 
diff --git a/packages/SettingsLib/tests/robotests/testutils/com/android/settingslib/testutils/shadow/ShadowUserManager.java b/packages/SettingsLib/tests/robotests/testutils/com/android/settingslib/testutils/shadow/ShadowUserManager.java
index ca1eefc..60d7721 100644
--- a/packages/SettingsLib/tests/robotests/testutils/com/android/settingslib/testutils/shadow/ShadowUserManager.java
+++ b/packages/SettingsLib/tests/robotests/testutils/com/android/settingslib/testutils/shadow/ShadowUserManager.java
@@ -16,20 +16,28 @@
 
 package com.android.settingslib.testutils.shadow;
 
+import static android.os.Build.VERSION_CODES.N_MR1;
+
 import android.annotation.UserIdInt;
 import android.content.Context;
 import android.content.pm.UserInfo;
+import android.content.pm.UserProperties;
+import android.os.UserHandle;
 import android.os.UserManager;
 
 import org.robolectric.annotation.Implementation;
 import org.robolectric.annotation.Implements;
+import org.robolectric.shadows.ShadowBuild;
 
 import java.util.ArrayList;
+import java.util.HashMap;
 import java.util.List;
+import java.util.Map;
 
 @Implements(value = UserManager.class)
 public class ShadowUserManager extends org.robolectric.shadows.ShadowUserManager {
     private List<UserInfo> mUserInfos = addProfile(0, "Owner");
+    private final Map<Integer, UserProperties> mUserPropertiesMap = new HashMap<>();
 
     @Implementation
     protected static UserManager get(Context context) {
@@ -62,4 +70,37 @@
     protected List<UserInfo> getProfiles(@UserIdInt int userHandle) {
         return getProfiles();
     }
+
+    /**
+     * @return {@code false} by default, or the value specified via {@link #setIsAdminUser(boolean)}
+     */
+    @Implementation(minSdk = N_MR1)
+    public boolean isAdminUser() {
+        return getUserInfo(UserHandle.myUserId()).isAdmin();
+    }
+
+    /**
+     * Sets that the current user is an admin user; controls the return value of
+     * {@link UserManager#isAdminUser}.
+     */
+    public void setIsAdminUser(boolean isAdminUser) {
+        UserInfo userInfo = getUserInfo(UserHandle.myUserId());
+        if (isAdminUser) {
+            userInfo.flags |= UserInfo.FLAG_ADMIN;
+        } else {
+            userInfo.flags &= ~UserInfo.FLAG_ADMIN;
+        }
+    }
+
+    public void setupUserProperty(int userId, int showInSettings) {
+        UserProperties userProperties = new UserProperties(new UserProperties.Builder()
+                .setShowInSettings(showInSettings).build());
+        mUserPropertiesMap.putIfAbsent(userId, userProperties);
+    }
+
+    @Implementation(minSdk = ShadowBuild.UPSIDE_DOWN_CAKE)
+    protected UserProperties getUserProperties(UserHandle user) {
+        return mUserPropertiesMap.getOrDefault(user.getIdentifier(),
+            new UserProperties(new UserProperties.Builder().build()));
+    }
 }
diff --git a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
index dd8eb3b..c740423 100644
--- a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
+++ b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
@@ -345,6 +345,8 @@
                     Settings.Global.MIN_DURATION_BETWEEN_RECOVERY_STEPS_IN_MS,
                     Settings.Global.MOBILE_DATA, // Candidate for backup?
                     Settings.Global.MOBILE_DATA_ALWAYS_ON,
+                    Settings.Global.DSRM_DURATION_MILLIS,
+                    Settings.Global.DSRM_ENABLED_ACTIONS,
                     Settings.Global.MODE_RINGER,
                     Settings.Global.MULTI_SIM_DATA_CALL_SUBSCRIPTION,
                     Settings.Global.MULTI_SIM_SMS_PROMPT,
diff --git a/packages/SystemUI/Android.bp b/packages/SystemUI/Android.bp
index a443b5c..73fb0f0 100644
--- a/packages/SystemUI/Android.bp
+++ b/packages/SystemUI/Android.bp
@@ -65,6 +65,7 @@
                 "androidx.compose.runtime_runtime",
                 "androidx.compose.material3_material3",
                 "androidx.activity_activity-compose",
+                "androidx.compose.animation_animation-graphics",
             ],
 
             // By default, Compose is disabled and we compile the ComposeFacade
diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml
index 2913c16..4fd4723 100644
--- a/packages/SystemUI/AndroidManifest.xml
+++ b/packages/SystemUI/AndroidManifest.xml
@@ -865,7 +865,7 @@
         <activity
             android:name=".settings.brightness.BrightnessDialog"
             android:label="@string/quick_settings_brightness_dialog_title"
-            android:theme="@style/Theme.SystemUI.QuickSettings.BrightnessDialog"
+            android:theme="@style/BrightnessDialog"
             android:finishOnCloseSystemDialogs="true"
             android:launchMode="singleInstance"
             android:excludeFromRecents="true"
diff --git a/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/compose/ComposeInitializerImpl.kt b/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/compose/ComposeInitializerImpl.kt
index fbd7f83..1674591 100644
--- a/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/compose/ComposeInitializerImpl.kt
+++ b/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/compose/ComposeInitializerImpl.kt
@@ -17,9 +17,9 @@
 package com.android.systemui.compose
 
 import android.view.View
+import androidx.lifecycle.findViewTreeLifecycleOwner
+import androidx.lifecycle.setViewTreeLifecycleOwner
 import androidx.lifecycle.Lifecycle
-import androidx.lifecycle.ViewTreeLifecycleOwner
-import androidx.savedstate.SavedStateRegistry
 import androidx.savedstate.SavedStateRegistryController
 import androidx.savedstate.SavedStateRegistryOwner
 import com.android.compose.animation.ViewTreeSavedStateRegistryOwner
@@ -27,7 +27,7 @@
 
 internal object ComposeInitializerImpl : ComposeInitializer {
     override fun onAttachedToWindow(root: View) {
-        if (ViewTreeLifecycleOwner.get(root) != null) {
+        if (root.findViewTreeLifecycleOwner() != null) {
             error("root $root already has a LifecycleOwner")
         }
 
@@ -54,7 +54,8 @@
 
                 override val savedStateRegistry = savedStateRegistryController.savedStateRegistry
 
-                override fun getLifecycle(): Lifecycle = lifecycleOwner.lifecycle
+                override val lifecycle: Lifecycle
+                    get() = lifecycleOwner.lifecycle
             }
 
         // We must call [ViewLifecycleOwner.onCreate] after creating the [SavedStateRegistryOwner]
@@ -64,13 +65,13 @@
 
         // Set the owners on the root. They will be reused by any ComposeView inside the root
         // hierarchy.
-        ViewTreeLifecycleOwner.set(root, lifecycleOwner)
+        root.setViewTreeLifecycleOwner(lifecycleOwner)
         ViewTreeSavedStateRegistryOwner.set(root, savedStateRegistryOwner)
     }
 
     override fun onDetachedFromWindow(root: View) {
-        (ViewTreeLifecycleOwner.get(root) as ViewLifecycleOwner).onDestroy()
-        ViewTreeLifecycleOwner.set(root, null)
+        (root.findViewTreeLifecycleOwner() as ViewLifecycleOwner).onDestroy()
+        root.setViewTreeLifecycleOwner(null)
         ViewTreeSavedStateRegistryOwner.set(root, null)
     }
 }
diff --git a/packages/SystemUI/compose/features/Android.bp b/packages/SystemUI/compose/features/Android.bp
index 8f8bb1b..c6438c9 100644
--- a/packages/SystemUI/compose/features/Android.bp
+++ b/packages/SystemUI/compose/features/Android.bp
@@ -34,6 +34,7 @@
         "PlatformComposeCore",
 
         "androidx.compose.runtime_runtime",
+        "androidx.compose.animation_animation-graphics",
         "androidx.compose.material3_material3",
         "androidx.activity_activity-compose",
     ],
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerScene.kt
index 240bace..d83596e 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerScene.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerScene.kt
@@ -99,7 +99,10 @@
         horizontalAlignment = Alignment.CenterHorizontally,
         verticalArrangement = Arrangement.spacedBy(60.dp),
         modifier =
-            modifier.background(MaterialTheme.colorScheme.surface).fillMaxSize().padding(32.dp)
+            modifier
+                .fillMaxSize()
+                .background(MaterialTheme.colorScheme.surface)
+                .padding(start = 32.dp, top = 92.dp, end = 32.dp, bottom = 32.dp)
     ) {
         Crossfade(
             targetState = message,
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PinBouncer.kt b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PinBouncer.kt
index 323fed0..85178bc 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PinBouncer.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PinBouncer.kt
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-@file:OptIn(ExperimentalAnimationApi::class)
+@file:OptIn(ExperimentalAnimationApi::class, ExperimentalAnimationGraphicsApi::class)
 
 package com.android.systemui.bouncer.ui.composable
 
@@ -29,11 +29,14 @@
 import androidx.compose.animation.core.animateDp
 import androidx.compose.animation.core.animateDpAsState
 import androidx.compose.animation.core.animateFloatAsState
-import androidx.compose.animation.core.keyframes
 import androidx.compose.animation.core.snap
 import androidx.compose.animation.core.tween
 import androidx.compose.animation.core.updateTransition
-import androidx.compose.foundation.Canvas
+import androidx.compose.animation.graphics.ExperimentalAnimationGraphicsApi
+import androidx.compose.animation.graphics.res.animatedVectorResource
+import androidx.compose.animation.graphics.res.rememberAnimatedVectorPainter
+import androidx.compose.animation.graphics.vector.AnimatedImageVector
+import androidx.compose.foundation.Image
 import androidx.compose.foundation.gestures.detectTapGestures
 import androidx.compose.foundation.layout.Box
 import androidx.compose.foundation.layout.Column
@@ -61,8 +64,10 @@
 import androidx.compose.ui.draw.drawBehind
 import androidx.compose.ui.geometry.CornerRadius
 import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.ColorFilter
 import androidx.compose.ui.graphics.graphicsLayer
 import androidx.compose.ui.input.pointer.pointerInput
+import androidx.compose.ui.layout.ContentScale
 import androidx.compose.ui.layout.Layout
 import androidx.compose.ui.platform.LocalView
 import androidx.compose.ui.unit.Constraints
@@ -70,6 +75,7 @@
 import androidx.compose.ui.unit.dp
 import com.android.compose.animation.Easings
 import com.android.compose.grid.VerticalGrid
+import com.android.internal.R.id.image
 import com.android.systemui.R
 import com.android.systemui.bouncer.ui.viewmodel.ActionButtonAppearance
 import com.android.systemui.bouncer.ui.viewmodel.EnteredKey
@@ -139,7 +145,8 @@
                         else -> EntryVisibility.Hidden
                     }
 
-                ObscuredInputEntry(updateTransition(visibility, label = "Pin Entry $entry"))
+                val shape = viewModel.pinShapes.getShape(entry.sequenceNumber)
+                PinInputEntry(shape, updateTransition(visibility, label = "Pin Entry $entry"))
 
                 LaunchedEffect(entry) {
                     // Remove entry from visiblePinEntries once the hide transition completed.
@@ -171,15 +178,11 @@
 }
 
 @Composable
-private fun ObscuredInputEntry(transition: Transition<EntryVisibility>) {
+private fun PinInputEntry(shapeResourceId: Int, transition: Transition<EntryVisibility>) {
     // spec: http://shortn/_DEhE3Xl2bi
-    val shapePadding = 6.dp
-    val shapeOvershootSize = 22.dp
     val dismissStaggerDelayMs = 33
     val dismissDurationMs = 450
     val expansionDurationMs = 250
-    val shapeExpandDurationMs = 83
-    val shapeRetractDurationMs = 167
     val shapeCollapseDurationMs = 200
 
     val animatedEntryWidth by
@@ -194,19 +197,17 @@
             },
             label = "entry space"
         ) { state ->
-            if (state == EntryVisibility.Shown) entryShapeSize + (shapePadding * 2) else 0.dp
+            if (state == EntryVisibility.Shown) entryShapeSize else 0.dp
         }
 
     val animatedShapeSize by
         transition.animateDp(
             transitionSpec = {
                 when {
-                    EntryVisibility.Hidden isTransitioningTo EntryVisibility.Shown ->
-                        keyframes {
-                            durationMillis = shapeExpandDurationMs + shapeRetractDurationMs
-                            0.dp at 0 with Easings.Linear
-                            shapeOvershootSize at shapeExpandDurationMs with Easings.Legacy
-                        }
+                    EntryVisibility.Hidden isTransitioningTo EntryVisibility.Shown -> {
+                        // The AVD contains the entry transition.
+                        snap()
+                    }
                     targetState is EntryVisibility.BulkHidden -> {
                         val target = targetState as EntryVisibility.BulkHidden
                         tween(
@@ -220,17 +221,21 @@
             },
             label = "shape size"
         ) { state ->
-            when (state) {
-                EntryVisibility.Shown -> entryShapeSize
-                else -> 0.dp
-            }
+            if (state == EntryVisibility.Shown) entryShapeSize else 0.dp
         }
 
     val dotColor = MaterialTheme.colorScheme.onSurfaceVariant
     Layout(
         content = {
-            // TODO(b/282730134): add support for dot shapes.
-            Canvas(Modifier) { drawCircle(dotColor) }
+            val image = AnimatedImageVector.animatedVectorResource(shapeResourceId)
+            var atEnd by remember { mutableStateOf(false) }
+            Image(
+                painter = rememberAnimatedVectorPainter(image, atEnd),
+                contentDescription = null,
+                contentScale = ContentScale.Crop,
+                colorFilter = ColorFilter.tint(dotColor),
+            )
+            LaunchedEffect(Unit) { atEnd = true }
         }
     ) { measurables, _ ->
         val shapeSizePx = animatedShapeSize.roundToPx()
@@ -507,7 +512,7 @@
     }
 }
 
-private val entryShapeSize = 16.dp
+private val entryShapeSize = 30.dp
 
 private val pinButtonSize = 84.dp
 private val pinButtonErrorShrinkFactor = 67.dp / pinButtonSize
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 8858738..e0051f5 100644
--- a/packages/SystemUI/log/src/com/android/systemui/log/LogBuffer.kt
+++ b/packages/SystemUI/log/src/com/android/systemui/log/LogBuffer.kt
@@ -21,6 +21,7 @@
 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.MessageBuffer
 import com.android.systemui.log.core.MessageInitializer
 import com.android.systemui.log.core.MessagePrinter
 import com.google.errorprone.annotations.CompileTimeConstant
@@ -77,7 +78,7 @@
     private val maxSize: Int,
     private val logcatEchoTracker: LogcatEchoTracker,
     private val systrace: Boolean = true,
-) {
+) : MessageBuffer {
     private val buffer = RingBuffer(maxSize) { LogMessageImpl.create() }
 
     private val echoMessageQueue: BlockingQueue<LogMessage>? =
@@ -178,11 +179,11 @@
      * store any relevant data on the message and then call [commit].
      */
     @Synchronized
-    fun obtain(
+    override fun obtain(
         tag: String,
         level: LogLevel,
         messagePrinter: MessagePrinter,
-        exception: Throwable? = null,
+        exception: Throwable?,
     ): LogMessage {
         if (!mutable) {
             return FROZEN_MESSAGE
@@ -199,7 +200,7 @@
      * have finished filling in its data fields. The message will be echoed to logcat if necessary.
      */
     @Synchronized
-    fun commit(message: LogMessage) {
+    override fun commit(message: LogMessage) {
         if (!mutable) {
             return
         }
diff --git a/packages/SystemUI/log/src/com/android/systemui/log/core/Logger.kt b/packages/SystemUI/log/src/com/android/systemui/log/core/Logger.kt
new file mode 100644
index 0000000..5729ab2
--- /dev/null
+++ b/packages/SystemUI/log/src/com/android/systemui/log/core/Logger.kt
@@ -0,0 +1,224 @@
+/*
+ * 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.core
+
+import com.google.errorprone.annotations.CompileTimeConstant
+
+/** Logs messages to the [MessageBuffer] with [tag]. */
+open class Logger(val buffer: MessageBuffer, val tag: String) {
+    /**
+     * Logs a message to the buffer.
+     *
+     * The actual string of the log message is not constructed until it is needed. To accomplish
+     * this, logging a message is a two-step process. First, a fresh instance of [LogMessage] is
+     * obtained and is passed to the [messageInitializer]. The initializer stores any relevant data
+     * on the message's fields. The message is then inserted into the buffer where it waits until it
+     * is either pushed out by newer messages or it needs to printed. If and when this latter moment
+     * occurs, the [messagePrinter] function is called on the message. It reads whatever data the
+     * initializer stored and converts it to a human-readable log message.
+     *
+     * @param level Which level to log the message at, both to the buffer and to logcat if it's
+     *   echoed. In general, a module should split most of its logs into either INFO or DEBUG level.
+     *   INFO level should be reserved for information that other parts of the system might care
+     *   about, leaving the specifics of code's day-to-day operations to DEBUG.
+     * @param messagePrinter 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 should ONLY ever reference fields on the [LogMessage]
+     *   and NEVER any variables in its enclosing scope. Otherwise, the runtime will need to
+     *   allocate a new instance of the printer for each call, thwarting our attempts at avoiding
+     *   any sort of allocation.
+     * @param exception Provide any exception that need to be logged. This is saved as
+     *   [LogMessage.exception]
+     * @param messageInitializer 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.
+     */
+    @JvmOverloads
+    inline fun log(
+        level: LogLevel,
+        noinline messagePrinter: MessagePrinter,
+        exception: Throwable? = null,
+        messageInitializer: MessageInitializer,
+    ) {
+        val message = buffer.obtain(tag, level, messagePrinter, exception)
+        messageInitializer(message)
+        buffer.commit(message)
+    }
+
+    /**
+     * Logs a compile-time string constant [message] to the log buffer. Use sparingly.
+     *
+     * This is for simpler use-cases where [message] is a compile time string constant. For
+     * use-cases where the log message is built during runtime, use the [log] overloaded method that
+     * takes in an initializer and a message printer.
+     *
+     * Buffers are limited by the number of entries, so logging more frequently will limit the time
+     * window that the [MessageBuffer] covers in a bug report. Richer logs, on the other hand, make
+     * a bug report more actionable, so using the [log] with a [MessagePrinter] to add more details
+     * to every log may do more to improve overall logging than adding more logs with this method.
+     */
+    @JvmOverloads
+    fun log(
+        level: LogLevel,
+        @CompileTimeConstant message: String,
+        exception: Throwable? = null,
+    ) = log(level, { str1!! }, exception) { str1 = message }
+
+    /**
+     * Logs a message to the buffer at [LogLevel.VERBOSE].
+     *
+     * @see log
+     */
+    @JvmOverloads
+    inline fun v(
+        noinline messagePrinter: MessagePrinter,
+        exception: Throwable? = null,
+        messageInitializer: MessageInitializer,
+    ) = log(LogLevel.VERBOSE, messagePrinter, exception, messageInitializer)
+
+    /**
+     * Logs a compile-time string constant [message] to the log buffer at [LogLevel.VERBOSE]. Use
+     * sparingly.
+     *
+     * @see log
+     */
+    @JvmOverloads
+    fun v(
+        @CompileTimeConstant message: String,
+        exception: Throwable? = null,
+    ) = log(LogLevel.VERBOSE, message, exception)
+
+    /**
+     * Logs a message to the buffer at [LogLevel.DEBUG].
+     *
+     * @see log
+     */
+    @JvmOverloads
+    inline fun d(
+        noinline messagePrinter: MessagePrinter,
+        exception: Throwable? = null,
+        messageInitializer: MessageInitializer,
+    ) = log(LogLevel.DEBUG, messagePrinter, exception, messageInitializer)
+
+    /**
+     * Logs a compile-time string constant [message] to the log buffer at [LogLevel.DEBUG]. Use
+     * sparingly.
+     *
+     * @see log
+     */
+    @JvmOverloads
+    fun d(
+        @CompileTimeConstant message: String,
+        exception: Throwable? = null,
+    ) = log(LogLevel.DEBUG, message, exception)
+
+    /**
+     * Logs a message to the buffer at [LogLevel.INFO].
+     *
+     * @see log
+     */
+    @JvmOverloads
+    inline fun i(
+        noinline messagePrinter: MessagePrinter,
+        exception: Throwable? = null,
+        messageInitializer: MessageInitializer,
+    ) = log(LogLevel.INFO, messagePrinter, exception, messageInitializer)
+
+    /**
+     * Logs a compile-time string constant [message] to the log buffer at [LogLevel.INFO]. Use
+     * sparingly.
+     *
+     * @see log
+     */
+    @JvmOverloads
+    fun i(
+        @CompileTimeConstant message: String,
+        exception: Throwable? = null,
+    ) = log(LogLevel.INFO, message, exception)
+
+    /**
+     * Logs a message to the buffer at [LogLevel.WARNING].
+     *
+     * @see log
+     */
+    @JvmOverloads
+    inline fun w(
+        noinline messagePrinter: MessagePrinter,
+        exception: Throwable? = null,
+        messageInitializer: MessageInitializer,
+    ) = log(LogLevel.WARNING, messagePrinter, exception, messageInitializer)
+
+    /**
+     * Logs a compile-time string constant [message] to the log buffer at [LogLevel.WARNING]. Use
+     * sparingly.
+     *
+     * @see log
+     */
+    @JvmOverloads
+    fun w(
+        @CompileTimeConstant message: String,
+        exception: Throwable? = null,
+    ) = log(LogLevel.WARNING, message, exception)
+
+    /**
+     * Logs a message to the buffer at [LogLevel.ERROR].
+     *
+     * @see log
+     */
+    @JvmOverloads
+    inline fun e(
+        noinline messagePrinter: MessagePrinter,
+        exception: Throwable? = null,
+        messageInitializer: MessageInitializer,
+    ) = log(LogLevel.ERROR, messagePrinter, exception, messageInitializer)
+
+    /**
+     * Logs a compile-time string constant [message] to the log buffer at [LogLevel.ERROR]. Use
+     * sparingly.
+     *
+     * @see log
+     */
+    @JvmOverloads
+    fun e(
+        @CompileTimeConstant message: String,
+        exception: Throwable? = null,
+    ) = log(LogLevel.ERROR, message, exception)
+
+    /**
+     * Logs a message to the buffer at [LogLevel.WTF].
+     *
+     * @see log
+     */
+    @JvmOverloads
+    inline fun wtf(
+        noinline messagePrinter: MessagePrinter,
+        exception: Throwable? = null,
+        messageInitializer: MessageInitializer,
+    ) = log(LogLevel.WTF, messagePrinter, exception, messageInitializer)
+
+    /**
+     * Logs a compile-time string constant [message] to the log buffer at [LogLevel.WTF]. Use
+     * sparingly.
+     *
+     * @see log
+     */
+    @JvmOverloads
+    fun wtf(
+        @CompileTimeConstant message: String,
+        exception: Throwable? = null,
+    ) = log(LogLevel.WTF, message, exception)
+}
diff --git a/packages/SystemUI/log/src/com/android/systemui/log/core/MessageBuffer.kt b/packages/SystemUI/log/src/com/android/systemui/log/core/MessageBuffer.kt
new file mode 100644
index 0000000..bb91633
--- /dev/null
+++ b/packages/SystemUI/log/src/com/android/systemui/log/core/MessageBuffer.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.log.core
+
+/**
+ * [MessageBuffer] is an interface that represents a buffer of log messages, and provides methods to
+ * [obtain] a log message and [commit] it to the buffer.
+ */
+interface MessageBuffer {
+    /**
+     * Obtains the next [LogMessage] from the buffer.
+     *
+     * After calling [obtain], the caller must store any relevant data on the message and then call
+     * [commit].
+     */
+    fun obtain(
+        tag: String,
+        level: LogLevel,
+        messagePrinter: MessagePrinter,
+        exception: Throwable? = null,
+    ): LogMessage
+
+    /**
+     * After acquiring a log message via [obtain], call this method to signal to the buffer that
+     * data fields have been filled.
+     */
+    fun commit(message: LogMessage)
+}
diff --git a/packages/SystemUI/plugin/bcsmartspace/src/com/android/systemui/plugins/BcSmartspaceDataPlugin.java b/packages/SystemUI/plugin/bcsmartspace/src/com/android/systemui/plugins/BcSmartspaceDataPlugin.java
index 1811c02..64c0f99 100644
--- a/packages/SystemUI/plugin/bcsmartspace/src/com/android/systemui/plugins/BcSmartspaceDataPlugin.java
+++ b/packages/SystemUI/plugin/bcsmartspace/src/com/android/systemui/plugins/BcSmartspaceDataPlugin.java
@@ -128,6 +128,16 @@
         void setDozeAmount(float amount);
 
         /**
+         * Set if dozing is true or false
+         */
+        default void setDozing(boolean dozing) {}
+
+        /**
+         * Set if split shade enabled
+         */
+        default void setSplitShadeEnabled(boolean enabled) {}
+
+        /**
          * Set the current keyguard bypass enabled status.
          */
         default void setKeyguardBypassEnabled(boolean enabled) {}
diff --git a/packages/SystemUI/res-keyguard/layout/udfps_keyguard_view.xml b/packages/SystemUI/res-keyguard/layout/udfps_keyguard_view.xml
new file mode 100644
index 0000000..360ef26
--- /dev/null
+++ b/packages/SystemUI/res-keyguard/layout/udfps_keyguard_view.xml
@@ -0,0 +1,25 @@
+<?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.
+  -->
+<com.android.systemui.biometrics.UdfpsKeyguardView
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/udfps_animation_view"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent">
+
+    <!-- Add fingerprint views here. See udfps_keyguard_view_internal.xml. -->
+
+</com.android.systemui.biometrics.UdfpsKeyguardView>
diff --git a/packages/SystemUI/res/drawable/pin_dot_avd.xml b/packages/SystemUI/res/drawable/pin_dot_avd.xml
index 1c16251..710ba83 100644
--- a/packages/SystemUI/res/drawable/pin_dot_avd.xml
+++ b/packages/SystemUI/res/drawable/pin_dot_avd.xml
@@ -1 +1,40 @@
-<animated-vector xmlns:android="http://schemas.android.com/apk/res/android" xmlns:aapt="http://schemas.android.com/aapt"><aapt:attr name="android:drawable"><vector android:height="30dp" android:width="30dp" android:viewportHeight="30" android:viewportWidth="30"><group android:name="_R_G"><group android:name="_R_G_L_0_G" android:translateX="28.237000000000002" android:translateY="23.112000000000002"><path android:name="_R_G_L_0_G_D_0_P_0" android:strokeColor="#ffffff" android:strokeLineCap="round" android:strokeLineJoin="round" android:strokeWidth="2" android:strokeAlpha="1" android:pathData=" M-13.24 -12.11 C-11.03,-12.11 -9.24,-10.32 -9.24,-8.11 C-9.24,-5.9 -11.03,-4.11 -13.24,-4.11 C-15.44,-4.11 -17.24,-5.9 -17.24,-8.11 C-17.24,-10.32 -15.44,-12.11 -13.24,-12.11c "/></group></group><group android:name="time_group"/></vector></aapt:attr><target android:name="time_group"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="translateX" android:duration="500" android:startOffset="0" android:valueFrom="0" android:valueTo="1" android:valueType="floatType"/></set></aapt:attr></target></animated-vector>
\ No newline at end of file
+<animated-vector xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:aapt="http://schemas.android.com/aapt">
+    <aapt:attr name="android:drawable">
+        <vector
+            android:height="30dp"
+            android:width="30dp"
+            android:viewportHeight="30"
+            android:viewportWidth="30">
+            <group android:name="_R_G">
+                <group
+                    android:name="_R_G_L_0_G"
+                    android:translateX="28.237000000000002"
+                    android:translateY="23.112000000000002">
+                    <path
+                        android:name="_R_G_L_0_G_D_0_P_0"
+                        android:strokeColor="#ffffff"
+                        android:strokeLineCap="round"
+                        android:strokeLineJoin="round"
+                        android:strokeWidth="2"
+                        android:strokeAlpha="1"
+                        android:pathData=" M-13.24 -12.11 C-11.03,-12.11 -9.24,-10.32 -9.24,-8.11 C-9.24,-5.9 -11.03,-4.11 -13.24,-4.11 C-15.44,-4.11 -17.24,-5.9 -17.24,-8.11 C-17.24,-10.32 -15.44,-12.11 -13.24,-12.11c " />
+                </group>
+            </group>
+            <group android:name="time_group" />
+        </vector>
+    </aapt:attr>
+    <target android:name="time_group">
+        <aapt:attr name="android:animation">
+            <set android:ordering="together">
+                <objectAnimator
+                    android:propertyName="translateX"
+                    android:duration="500"
+                    android:startOffset="0"
+                    android:valueFrom="0"
+                    android:valueTo="1"
+                    android:valueType="floatType" />
+            </set>
+        </aapt:attr>
+    </target>
+</animated-vector>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/pin_dot_delete_avd.xml b/packages/SystemUI/res/drawable/pin_dot_delete_avd.xml
index 0f8703f..72a03bf 100644
--- a/packages/SystemUI/res/drawable/pin_dot_delete_avd.xml
+++ b/packages/SystemUI/res/drawable/pin_dot_delete_avd.xml
@@ -1 +1,130 @@
-<animated-vector xmlns:android="http://schemas.android.com/apk/res/android" xmlns:aapt="http://schemas.android.com/aapt"><aapt:attr name="android:drawable"><vector android:height="30dp" android:width="30dp" android:viewportHeight="30" android:viewportWidth="30"><group android:name="_R_G"><group android:name="_R_G_L_0_G" android:translateX="28.54" android:translateY="23.54"><path android:name="_R_G_L_0_G_D_0_P_0" android:fillColor="#ffffff" android:fillAlpha="1" android:fillType="nonZero" android:pathData=" M-13.54 -16.54 C-9.12,-16.54 -5.54,-12.96 -5.54,-8.54 C-5.54,-4.12 -9.12,-0.54 -13.54,-0.54 C-17.96,-0.54 -21.54,-4.12 -21.54,-8.54 C-21.54,-12.96 -17.96,-16.54 -13.54,-16.54c "/><path android:name="_R_G_L_0_G_D_1_P_0" android:strokeColor="#ffffff" android:strokeLineCap="round" android:strokeLineJoin="round" android:strokeWidth="0" android:strokeAlpha="1" android:pathData=" M-13.54 -16.54 C-9.12,-16.54 -5.54,-12.96 -5.54,-8.54 C-5.54,-4.12 -9.12,-0.54 -13.54,-0.54 C-17.96,-0.54 -21.54,-4.12 -21.54,-8.54 C-21.54,-12.96 -17.96,-16.54 -13.54,-16.54c "/></group></group><group android:name="time_group"/></vector></aapt:attr><target android:name="_R_G_L_0_G_D_0_P_0"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="fillAlpha" android:duration="150" android:startOffset="0" android:valueFrom="1" android:valueTo="1" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="fillAlpha" android:duration="200" android:startOffset="150" android:valueFrom="1" android:valueTo="0" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0"/></aapt:attr></objectAnimator></set></aapt:attr></target><target android:name="_R_G_L_0_G_D_0_P_0"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="pathData" android:duration="150" android:startOffset="0" android:valueFrom="M-13.54 -16.54 C-9.12,-16.54 -5.54,-12.96 -5.54,-8.54 C-5.54,-4.12 -9.12,-0.54 -13.54,-0.54 C-17.96,-0.54 -21.54,-4.12 -21.54,-8.54 C-21.54,-12.96 -17.96,-16.54 -13.54,-16.54c " android:valueTo="M-13.54 -11.54 C-11.88,-11.54 -10.54,-10.2 -10.54,-8.54 C-10.54,-6.88 -11.88,-5.54 -13.54,-5.54 C-15.2,-5.54 -16.54,-6.88 -16.54,-8.54 C-16.54,-10.2 -15.2,-11.54 -13.54,-11.54c " android:valueType="pathType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="pathData" android:duration="200" android:startOffset="150" android:valueFrom="M-13.54 -11.54 C-11.88,-11.54 -10.54,-10.2 -10.54,-8.54 C-10.54,-6.88 -11.88,-5.54 -13.54,-5.54 C-15.2,-5.54 -16.54,-6.88 -16.54,-8.54 C-16.54,-10.2 -15.2,-11.54 -13.54,-11.54c " android:valueTo="M-13.54 -12.54 C-11.33,-12.54 -9.54,-10.75 -9.54,-8.54 C-9.54,-6.33 -11.33,-4.54 -13.54,-4.54 C-15.75,-4.54 -17.54,-6.33 -17.54,-8.54 C-17.54,-10.75 -15.75,-12.54 -13.54,-12.54c " android:valueType="pathType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0"/></aapt:attr></objectAnimator></set></aapt:attr></target><target android:name="_R_G_L_0_G_D_1_P_0"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="strokeWidth" android:duration="150" android:startOffset="0" android:valueFrom="0" android:valueTo="0" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="strokeWidth" android:duration="50" android:startOffset="150" android:valueFrom="0" android:valueTo="2" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0"/></aapt:attr></objectAnimator></set></aapt:attr></target><target android:name="_R_G_L_0_G_D_1_P_0"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="pathData" android:duration="150" android:startOffset="0" android:valueFrom="M-13.54 -16.54 C-9.12,-16.54 -5.54,-12.96 -5.54,-8.54 C-5.54,-4.12 -9.12,-0.54 -13.54,-0.54 C-17.96,-0.54 -21.54,-4.12 -21.54,-8.54 C-21.54,-12.96 -17.96,-16.54 -13.54,-16.54c " android:valueTo="M-13.54 -11.54 C-11.88,-11.54 -10.54,-10.2 -10.54,-8.54 C-10.54,-6.88 -11.88,-5.54 -13.54,-5.54 C-15.2,-5.54 -16.54,-6.88 -16.54,-8.54 C-16.54,-10.2 -15.2,-11.54 -13.54,-11.54c " android:valueType="pathType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="pathData" android:duration="200" android:startOffset="150" android:valueFrom="M-13.54 -11.54 C-11.88,-11.54 -10.54,-10.2 -10.54,-8.54 C-10.54,-6.88 -11.88,-5.54 -13.54,-5.54 C-15.2,-5.54 -16.54,-6.88 -16.54,-8.54 C-16.54,-10.2 -15.2,-11.54 -13.54,-11.54c " android:valueTo="M-13.54 -12.54 C-11.33,-12.54 -9.54,-10.75 -9.54,-8.54 C-9.54,-6.33 -11.33,-4.54 -13.54,-4.54 C-15.75,-4.54 -17.54,-6.33 -17.54,-8.54 C-17.54,-10.75 -15.75,-12.54 -13.54,-12.54c " android:valueType="pathType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0"/></aapt:attr></objectAnimator></set></aapt:attr></target><target android:name="time_group"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="translateX" android:duration="500" android:startOffset="0" android:valueFrom="0" android:valueTo="1" android:valueType="floatType"/></set></aapt:attr></target></animated-vector>
\ No newline at end of file
+<animated-vector xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:aapt="http://schemas.android.com/aapt">
+    <target android:name="_R_G_L_1_G">
+        <aapt:attr name="android:animation">
+            <set android:ordering="together">
+                <objectAnimator
+                    android:duration="150"
+                    android:propertyName="scaleX"
+                    android:startOffset="0"
+                    android:valueFrom="1"
+                    android:valueTo="0.43"
+                    android:valueType="floatType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
+                    </aapt:attr>
+                </objectAnimator>
+                <objectAnimator
+                    android:duration="150"
+                    android:propertyName="scaleY"
+                    android:startOffset="0"
+                    android:valueFrom="1"
+                    android:valueTo="0.43"
+                    android:valueType="floatType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
+                    </aapt:attr>
+                </objectAnimator>
+            </set>
+        </aapt:attr>
+    </target>
+    <target android:name="_R_G_L_1_G_D_0_P_0">
+        <aapt:attr name="android:animation">
+            <set android:ordering="together">
+                <objectAnimator
+                    android:propertyName="fillAlpha"
+                    android:duration="200"
+                    android:startOffset="150"
+                    android:valueFrom="1"
+                    android:valueTo="0"
+                    android:valueType="floatType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
+                    </aapt:attr>
+                </objectAnimator>
+            </set>
+        </aapt:attr>
+    </target>
+    <target android:name="_R_G_L_0_G">
+        <aapt:attr name="android:animation">
+            <set android:ordering="together">
+                <objectAnimator
+                    android:duration="200"
+                    android:propertyName="scaleX"
+                    android:startOffset="150"
+                    android:valueFrom="0.65"
+                    android:valueTo="1"
+                    android:valueType="floatType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
+                    </aapt:attr>
+                </objectAnimator>
+                <objectAnimator
+                    android:duration="200"
+                    android:propertyName="scaleY"
+                    android:startOffset="150"
+                    android:valueFrom="0.65"
+                    android:valueTo="1"
+                    android:valueType="floatType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
+                    </aapt:attr>
+                </objectAnimator>
+            </set>
+        </aapt:attr>
+    </target>
+    <target android:name="time_group">
+        <aapt:attr name="android:animation">
+            <set android:ordering="together">
+                <objectAnimator
+                    android:duration="500"
+                    android:propertyName="translateX"
+                    android:startOffset="0"
+                    android:valueFrom="0"
+                    android:valueTo="1"
+                    android:valueType="floatType" />
+            </set>
+        </aapt:attr>
+    </target>
+    <aapt:attr name="android:drawable">
+        <vector
+            android:width="30dp"
+            android:height="30dp"
+            android:viewportHeight="30"
+            android:viewportWidth="30">
+            <group android:name="_R_G">
+                <group
+                    android:name="_R_G_L_1_G"
+                    android:pivotX="-13.54"
+                    android:pivotY="-8.54"
+                    android:scaleX="1"
+                    android:scaleY="1"
+                    android:translateX="28.54"
+                    android:translateY="23.54">
+                    <path
+                        android:name="_R_G_L_1_G_D_0_P_0"
+                        android:fillAlpha="1"
+                        android:fillColor="#ffffff"
+                        android:fillType="nonZero"
+                        android:pathData=" M-13.54 -16.54 C-9.12,-16.54 -5.54,-12.96 -5.54,-8.54 C-5.54,-4.12 -9.12,-0.54 -13.54,-0.54 C-17.96,-0.54 -21.54,-4.12 -21.54,-8.54 C-21.54,-12.96 -17.96,-16.54 -13.54,-16.54c " />
+                </group>
+                <group
+                    android:name="_R_G_L_0_G"
+                    android:pivotX="317.509"
+                    android:pivotY="-826.986"
+                    android:scaleX="0.65"
+                    android:scaleY="0.65"
+                    android:translateX="-302.509"
+                    android:translateY="841.986">
+                    <path
+                        android:name="_R_G_L_0_G_D_0_P_0"
+                        android:fillAlpha="1"
+                        android:fillColor="#ffffff"
+                        android:fillType="nonZero"
+                        android:pathData=" M317.51 -821.99 C314.75,-821.99 312.51,-824.23 312.51,-826.99 C312.51,-829.74 314.75,-831.99 317.51,-831.99 C320.27,-831.99 322.51,-829.74 322.51,-826.99 C322.51,-824.23 320.27,-821.99 317.51,-821.99z  M317.51 -829.99 C315.86,-829.99 314.51,-828.64 314.51,-826.99 C314.51,-825.33 315.86,-823.99 317.51,-823.99 C319.16,-823.99 320.51,-825.33 320.51,-826.99 C320.51,-828.64 319.16,-829.99 317.51,-829.99z " />
+                </group>
+            </group>
+            <group android:name="time_group" />
+        </vector>
+    </aapt:attr>
+</animated-vector>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/pin_dot_shape_1_avd.xml b/packages/SystemUI/res/drawable/pin_dot_shape_1_avd.xml
index f1fb2aa..02abb99 100644
--- a/packages/SystemUI/res/drawable/pin_dot_shape_1_avd.xml
+++ b/packages/SystemUI/res/drawable/pin_dot_shape_1_avd.xml
@@ -1 +1,141 @@
-<animated-vector xmlns:android="http://schemas.android.com/apk/res/android" xmlns:aapt="http://schemas.android.com/aapt"><aapt:attr name="android:drawable"><vector android:height="30dp" android:width="30dp" android:viewportHeight="30" android:viewportWidth="30"><group android:name="_R_G"><group android:name="_R_G_L_1_G" android:translateX="15.441" android:translateY="15.691" android:scaleY="0"><path android:name="_R_G_L_1_G_D_0_P_0" android:fillColor="#ffffff" android:fillAlpha="1" android:fillType="nonZero" android:pathData=" M-0.44 -12.06 C13.5,-14.63 13.5,-14.63 10.9,-0.69 C13.5,13.25 13.5,13.25 -0.44,10.53 C-14.38,13.25 -14.38,13.25 -11.86,-0.69 C-14.38,-14.63 -14.38,-14.63 -0.44,-12.06c "/></group><group android:name="_R_G_L_0_G" android:translateX="15" android:translateY="15" android:scaleX="0" android:scaleY="0"><path android:name="_R_G_L_0_G_D_0_P_0" android:fillColor="#ffffff" android:fillAlpha="1" android:fillType="nonZero" android:pathData=" M-13.65 -3.95 C-16.31,-10.09 -10.09,-16.31 -3.95,-13.65 C-3.95,-13.65 -2.94,-13.21 -2.94,-13.21 C-1.06,-12.39 1.06,-12.39 2.94,-13.21 C2.94,-13.21 3.95,-13.65 3.95,-13.65 C10.09,-16.31 16.31,-10.09 13.65,-3.95 C13.65,-3.95 13.21,-2.94 13.21,-2.94 C12.39,-1.06 12.39,1.06 13.21,2.94 C13.21,2.94 13.65,3.95 13.65,3.95 C16.31,10.09 10.09,16.31 3.95,13.65 C3.95,13.65 2.94,13.21 2.94,13.21 C1.06,12.39 -1.06,12.39 -2.94,13.21 C-2.94,13.21 -3.95,13.65 -3.95,13.65 C-10.09,16.31 -16.31,10.09 -13.65,3.95 C-13.65,3.95 -13.21,2.94 -13.21,2.94 C-12.39,1.06 -12.39,-1.06 -13.21,-2.94 C-13.21,-2.94 -13.65,-3.95 -13.65,-3.95c "/></group></group><group android:name="time_group"/></vector></aapt:attr><target android:name="_R_G_L_1_G_D_0_P_0"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="pathData" android:duration="67" android:startOffset="0" android:valueFrom="M-0.44 -12.06 C13.5,-14.63 13.5,-14.63 10.9,-0.69 C13.5,13.25 13.5,13.25 -0.44,10.53 C-14.38,13.25 -14.38,13.25 -11.86,-0.69 C-14.38,-14.63 -14.38,-14.63 -0.44,-12.06c " android:valueTo="M-0.44 -12.06 C13.5,-14.63 13.5,-14.63 10.9,-0.69 C13.5,13.25 13.5,13.25 -0.44,10.53 C-14.38,13.25 -14.38,13.25 -11.86,-0.69 C-14.38,-14.63 -14.38,-14.63 -0.44,-12.06c " android:valueType="pathType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.3,0 0.8,1 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="pathData" android:duration="283" android:startOffset="67" android:valueFrom="M-0.44 -12.06 C13.5,-14.63 13.5,-14.63 10.9,-0.69 C13.5,13.25 13.5,13.25 -0.44,10.53 C-14.38,13.25 -14.38,13.25 -11.86,-0.69 C-14.38,-14.63 -14.38,-14.63 -0.44,-12.06c " android:valueTo="M-0.44 -8.69 C3.98,-8.69 7.56,-5.11 7.56,-0.69 C7.56,3.73 3.98,7.31 -0.44,7.31 C-4.86,7.31 -8.44,3.73 -8.44,-0.69 C-8.44,-5.11 -4.86,-8.69 -0.44,-8.69c " android:valueType="pathType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.3,0 0.8,1 1.0,1.0"/></aapt:attr></objectAnimator></set></aapt:attr></target><target android:name="_R_G_L_1_G"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="scaleY" android:duration="0" android:startOffset="67" android:valueFrom="0" android:valueTo="1" android:valueType="floatType"/></set></aapt:attr></target><target android:name="_R_G_L_0_G"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="scaleX" android:duration="67" android:startOffset="0" android:valueFrom="0" android:valueTo="1" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="scaleY" android:duration="67" android:startOffset="0" android:valueFrom="0" android:valueTo="1" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="scaleX" android:duration="283" android:startOffset="67" android:valueFrom="1" android:valueTo="0.3" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.7,0 0.6,1 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="scaleY" android:duration="283" android:startOffset="67" android:valueFrom="1" android:valueTo="0.3" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.7,0 0.6,1 1.0,1.0"/></aapt:attr></objectAnimator></set></aapt:attr></target><target android:name="_R_G_L_0_G"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="scaleY" android:duration="0" android:startOffset="0" android:valueFrom="0" android:valueTo="1" android:valueType="floatType"/></set></aapt:attr></target><target android:name="_R_G_L_0_G"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="scaleY" android:duration="0" android:startOffset="367" android:valueFrom="1" android:valueTo="0" android:valueType="floatType"/></set></aapt:attr></target><target android:name="time_group"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="translateX" android:duration="500" android:startOffset="0" android:valueFrom="0" android:valueTo="1" android:valueType="floatType"/></set></aapt:attr></target></animated-vector>
\ No newline at end of file
+<animated-vector xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:aapt="http://schemas.android.com/aapt">
+    <target android:name="_R_G_L_1_G_D_0_P_0">
+        <aapt:attr name="android:animation">
+            <set android:ordering="together">
+                <objectAnimator
+                    android:duration="67"
+                    android:propertyName="pathData"
+                    android:startOffset="0"
+                    android:valueFrom="M-0.44 -12.06 C13.5,-14.63 13.5,-14.63 10.9,-0.69 C13.5,13.25 13.5,13.25 -0.44,10.53 C-14.38,13.25 -14.38,13.25 -11.86,-0.69 C-14.38,-14.63 -14.38,-14.63 -0.44,-12.06c "
+                    android:valueTo="M-0.44 -12.06 C13.5,-14.63 13.5,-14.63 10.9,-0.69 C13.5,13.25 13.5,13.25 -0.44,10.53 C-14.38,13.25 -14.38,13.25 -11.86,-0.69 C-14.38,-14.63 -14.38,-14.63 -0.44,-12.06c "
+                    android:valueType="pathType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.3,0 0.8,1 1.0,1.0" />
+                    </aapt:attr>
+                </objectAnimator>
+                <objectAnimator
+                    android:duration="283"
+                    android:propertyName="pathData"
+                    android:startOffset="67"
+                    android:valueFrom="M-0.44 -12.06 C13.5,-14.63 13.5,-14.63 10.9,-0.69 C13.5,13.25 13.5,13.25 -0.44,10.53 C-14.38,13.25 -14.38,13.25 -11.86,-0.69 C-14.38,-14.63 -14.38,-14.63 -0.44,-12.06c "
+                    android:valueTo="M-0.44 -8.69 C3.98,-8.69 7.56,-5.11 7.56,-0.69 C7.56,3.73 3.98,7.31 -0.44,7.31 C-4.86,7.31 -8.44,3.73 -8.44,-0.69 C-8.44,-5.11 -4.86,-8.69 -0.44,-8.69c "
+                    android:valueType="pathType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.3,0 0.8,1 1.0,1.0" />
+                    </aapt:attr>
+                </objectAnimator>
+            </set>
+        </aapt:attr>
+    </target>
+    <target android:name="_R_G_L_1_G">
+        <aapt:attr name="android:animation">
+            <set android:ordering="together">
+                <objectAnimator
+                    android:duration="0"
+                    android:propertyName="scaleY"
+                    android:startOffset="67"
+                    android:valueFrom="0"
+                    android:valueTo="1"
+                    android:valueType="floatType" />
+            </set>
+        </aapt:attr>
+    </target>
+    <target android:name="_R_G_L_0_G">
+        <aapt:attr name="android:animation">
+            <set android:ordering="together">
+                <objectAnimator
+                    android:duration="67"
+                    android:propertyName="scaleX"
+                    android:startOffset="0"
+                    android:valueFrom="0"
+                    android:valueTo="1"
+                    android:valueType="floatType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
+                    </aapt:attr>
+                </objectAnimator>
+                <objectAnimator
+                    android:duration="67"
+                    android:propertyName="scaleY"
+                    android:startOffset="0"
+                    android:valueFrom="0"
+                    android:valueTo="1"
+                    android:valueType="floatType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
+                    </aapt:attr>
+                </objectAnimator>
+                <objectAnimator
+                    android:duration="283"
+                    android:propertyName="scaleX"
+                    android:startOffset="67"
+                    android:valueFrom="1"
+                    android:valueTo="0.3"
+                    android:valueType="floatType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.7,0 0.6,1 1.0,1.0" />
+                    </aapt:attr>
+                </objectAnimator>
+                <objectAnimator
+                    android:duration="283"
+                    android:propertyName="scaleY"
+                    android:startOffset="67"
+                    android:valueFrom="1"
+                    android:valueTo="0.3"
+                    android:valueType="floatType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.7,0 0.6,1 1.0,1.0" />
+                    </aapt:attr>
+                </objectAnimator>
+            </set>
+        </aapt:attr>
+    </target>
+    <target android:name="time_group">
+        <aapt:attr name="android:animation">
+            <set android:ordering="together">
+                <objectAnimator
+                    android:duration="500"
+                    android:propertyName="translateX"
+                    android:startOffset="0"
+                    android:valueFrom="0"
+                    android:valueTo="1"
+                    android:valueType="floatType" />
+            </set>
+        </aapt:attr>
+    </target>
+    <aapt:attr name="android:drawable">
+        <vector
+            android:width="30dp"
+            android:height="30dp"
+            android:viewportHeight="30"
+            android:viewportWidth="30">
+            <group android:name="_R_G">
+                <group
+                    android:name="_R_G_L_1_G"
+                    android:scaleY="0"
+                    android:translateX="15.441"
+                    android:translateY="15.691">
+                    <path
+                        android:name="_R_G_L_1_G_D_0_P_0"
+                        android:fillAlpha="1"
+                        android:fillColor="#ffffff"
+                        android:fillType="nonZero"
+                        android:pathData=" M-0.44 -12.06 C13.5,-14.63 13.5,-14.63 10.9,-0.69 C13.5,13.25 13.5,13.25 -0.44,10.53 C-14.38,13.25 -14.38,13.25 -11.86,-0.69 C-14.38,-14.63 -14.38,-14.63 -0.44,-12.06c " />
+                </group>
+                <group
+                    android:name="_R_G_L_0_G"
+                    android:translateX="15"
+                    android:translateY="15">
+                    <path
+                        android:name="_R_G_L_0_G_D_0_P_0"
+                        android:fillAlpha="1"
+                        android:fillColor="#ffffff"
+                        android:fillType="nonZero"
+                        android:pathData=" M-13.65 -3.95 C-16.31,-10.09 -10.09,-16.31 -3.95,-13.65 C-3.95,-13.65 -2.94,-13.21 -2.94,-13.21 C-1.06,-12.39 1.06,-12.39 2.94,-13.21 C2.94,-13.21 3.95,-13.65 3.95,-13.65 C10.09,-16.31 16.31,-10.09 13.65,-3.95 C13.65,-3.95 13.21,-2.94 13.21,-2.94 C12.39,-1.06 12.39,1.06 13.21,2.94 C13.21,2.94 13.65,3.95 13.65,3.95 C16.31,10.09 10.09,16.31 3.95,13.65 C3.95,13.65 2.94,13.21 2.94,13.21 C1.06,12.39 -1.06,12.39 -2.94,13.21 C-2.94,13.21 -3.95,13.65 -3.95,13.65 C-10.09,16.31 -16.31,10.09 -13.65,3.95 C-13.65,3.95 -13.21,2.94 -13.21,2.94 C-12.39,1.06 -12.39,-1.06 -13.21,-2.94 C-13.21,-2.94 -13.65,-3.95 -13.65,-3.95c " />
+                </group>
+            </group>
+            <group android:name="time_group" />
+        </vector>
+    </aapt:attr>
+</animated-vector>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/pin_dot_shape_2_avd.xml b/packages/SystemUI/res/drawable/pin_dot_shape_2_avd.xml
index 3717db8..1607327 100644
--- a/packages/SystemUI/res/drawable/pin_dot_shape_2_avd.xml
+++ b/packages/SystemUI/res/drawable/pin_dot_shape_2_avd.xml
@@ -1 +1,148 @@
-<animated-vector xmlns:android="http://schemas.android.com/apk/res/android" xmlns:aapt="http://schemas.android.com/aapt"><aapt:attr name="android:drawable"><vector android:height="30dp" android:width="30dp" android:viewportHeight="30" android:viewportWidth="30"><group android:name="_R_G"><group android:name="_R_G_L_1_G" android:translateX="15.397" android:translateY="15.691" android:scaleY="0"><path android:name="_R_G_L_1_G_D_0_P_0" android:fillColor="#ffffff" android:fillAlpha="1" android:fillType="nonZero" android:pathData=" M-0.56 -14.03 C3.65,-13.99 14.58,7.64 11.51,10.42 C8.45,13.2 5.92,9.56 -0.46,9.61 C-6.85,9.65 -9.27,12.76 -12.33,10.46 C-15.39,8.15 -4.77,-14.07 -0.56,-14.03c "/></group><group android:name="_R_G_L_0_G" android:translateX="15" android:translateY="13.205" android:pivotY="1.795" android:scaleX="0" android:scaleY="0"><path android:name="_R_G_L_0_G_D_0_P_0" android:fillColor="#ffffff" android:fillAlpha="1" android:fillType="nonZero" android:pathData=" M12.78 7.57 C12.78,7.57 4.72,-8.55 4.72,-8.55 C2.77,-12.44 -2.77,-12.44 -4.72,-8.55 C-4.72,-8.55 -12.78,7.57 -12.78,7.57 C-15,12.01 -10.42,16.78 -5.89,14.74 C-5.89,14.74 -2.17,13.07 -2.17,13.07 C-0.79,12.45 0.79,12.45 2.17,13.07 C2.17,13.07 5.9,14.74 5.9,14.74 C10.42,16.78 15,12.01 12.78,7.57c "/></group></group><group android:name="time_group"/></vector></aapt:attr><target android:name="_R_G_L_1_G_D_0_P_0"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="pathData" android:duration="67" android:startOffset="0" android:valueFrom="M-0.56 -14.03 C3.65,-13.99 14.58,7.64 11.51,10.42 C8.45,13.2 5.92,9.56 -0.46,9.61 C-6.85,9.65 -9.27,12.76 -12.33,10.46 C-15.39,8.15 -4.77,-14.07 -0.56,-14.03c " android:valueTo="M-0.56 -14.03 C3.65,-13.99 14.58,7.64 11.51,10.42 C8.45,13.2 5.92,9.56 -0.46,9.61 C-6.85,9.65 -9.27,12.76 -12.33,10.46 C-15.39,8.15 -4.77,-14.07 -0.56,-14.03c " android:valueType="pathType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.3,0 0.8,1 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="pathData" android:duration="283" android:startOffset="67" android:valueFrom="M-0.56 -14.03 C3.65,-13.99 14.58,7.64 11.51,10.42 C8.45,13.2 5.92,9.56 -0.46,9.61 C-6.85,9.65 -9.27,12.76 -12.33,10.46 C-15.39,8.15 -4.77,-14.07 -0.56,-14.03c " android:valueTo="M-0.44 -8.69 C3.98,-8.69 7.56,-5.11 7.56,-0.69 C7.56,3.73 3.98,7.31 -0.44,7.31 C-4.86,7.31 -8.44,3.73 -8.44,-0.69 C-8.44,-5.11 -4.86,-8.69 -0.44,-8.69c " android:valueType="pathType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.3,0 0.8,1 1.0,1.0"/></aapt:attr></objectAnimator></set></aapt:attr></target><target android:name="_R_G_L_1_G"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="scaleY" android:duration="0" android:startOffset="67" android:valueFrom="0" android:valueTo="1" android:valueType="floatType"/></set></aapt:attr></target><target android:name="_R_G_L_0_G_D_0_P_0"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="pathData" android:duration="67" android:startOffset="0" android:valueFrom="M12.78 7.57 C12.78,7.57 4.72,-8.55 4.72,-8.55 C2.77,-12.44 -2.77,-12.44 -4.72,-8.55 C-4.72,-8.55 -12.78,7.57 -12.78,7.57 C-15,12.01 -10.42,16.78 -5.89,14.74 C-5.89,14.74 -2.17,13.07 -2.17,13.07 C-0.79,12.45 0.79,12.45 2.17,13.07 C2.17,13.07 5.9,14.74 5.9,14.74 C10.42,16.78 15,12.01 12.78,7.57c " android:valueTo="M12.78 7.57 C12.78,7.57 4.72,-8.55 4.72,-8.55 C2.77,-12.44 -2.77,-12.44 -4.72,-8.55 C-4.72,-8.55 -12.78,7.57 -12.78,7.57 C-15,12.01 -10.42,16.78 -5.89,14.74 C-5.89,14.74 -2.17,13.07 -2.17,13.07 C-0.79,12.45 0.79,12.45 2.17,13.07 C2.17,13.07 5.9,14.74 5.9,14.74 C10.42,16.78 15,12.01 12.78,7.57c " android:valueType="pathType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.3,0 0.833,1 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="pathData" android:duration="283" android:startOffset="67" android:valueFrom="M12.78 7.57 C12.78,7.57 4.72,-8.55 4.72,-8.55 C2.77,-12.44 -2.77,-12.44 -4.72,-8.55 C-4.72,-8.55 -12.78,7.57 -12.78,7.57 C-15,12.01 -10.42,16.78 -5.89,14.74 C-5.89,14.74 -2.17,13.07 -2.17,13.07 C-0.79,12.45 0.79,12.45 2.17,13.07 C2.17,13.07 5.9,14.74 5.9,14.74 C10.42,16.78 15,12.01 12.78,7.57c " android:valueTo="M6.12 0.21 C6.12,0.21 2.27,-4.35 2.27,-4.35 C1.34,-6.2 -1.3,-6.2 -2.23,-4.35 C-2.23,-4.35 -6.06,0.21 -6.06,0.21 C-7.12,2.33 -5.46,5.79 -4.03,6.54 C-2.28,7.45 -1.01,7.48 -1.01,7.48 C-1.01,7.48 1.05,7.48 1.05,7.48 C1.05,7.48 3.28,7.36 4.23,6.66 C4.92,6.15 7.18,2.33 6.12,0.21c " android:valueType="pathType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.3,0 0.833,1 1.0,1.0"/></aapt:attr></objectAnimator></set></aapt:attr></target><target android:name="_R_G_L_0_G"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="scaleX" android:duration="67" android:startOffset="0" android:valueFrom="0" android:valueTo="1" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="scaleY" android:duration="67" android:startOffset="0" android:valueFrom="0" android:valueTo="1" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0"/></aapt:attr></objectAnimator></set></aapt:attr></target><target android:name="_R_G_L_0_G"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="scaleY" android:duration="0" android:startOffset="0" android:valueFrom="0" android:valueTo="1" android:valueType="floatType"/></set></aapt:attr></target><target android:name="_R_G_L_0_G"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="scaleY" android:duration="0" android:startOffset="367" android:valueFrom="1" android:valueTo="0" android:valueType="floatType"/></set></aapt:attr></target><target android:name="time_group"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="translateX" android:duration="500" android:startOffset="0" android:valueFrom="0" android:valueTo="1" android:valueType="floatType"/></set></aapt:attr></target></animated-vector>
\ No newline at end of file
+<animated-vector xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:aapt="http://schemas.android.com/aapt">
+    <aapt:attr name="android:drawable">
+        <vector
+            android:height="30dp"
+            android:width="30dp"
+            android:viewportHeight="30"
+            android:viewportWidth="30">
+            <group android:name="_R_G">
+                <group
+                    android:name="_R_G_L_1_G"
+                    android:translateX="15.397"
+                    android:translateY="15.691"
+                    android:scaleY="0">
+                    <path
+                        android:name="_R_G_L_1_G_D_0_P_0"
+                        android:fillColor="#ffffff"
+                        android:fillAlpha="1"
+                        android:fillType="nonZero"
+                        android:pathData=" M-0.56 -14.03 C3.65,-13.99 14.58,7.64 11.51,10.42 C8.45,13.2 5.92,9.56 -0.46,9.61 C-6.85,9.65 -9.27,12.76 -12.33,10.46 C-15.39,8.15 -4.77,-14.07 -0.56,-14.03c " />
+                </group>
+                <group
+                    android:name="_R_G_L_0_G"
+                    android:translateX="15"
+                    android:translateY="13.205"
+                    android:pivotY="1.795">
+                    <path
+                        android:name="_R_G_L_0_G_D_0_P_0"
+                        android:fillColor="#ffffff"
+                        android:fillAlpha="1"
+                        android:fillType="nonZero"
+                        android:pathData=" M12.78 7.57 C12.78,7.57 4.72,-8.55 4.72,-8.55 C2.77,-12.44 -2.77,-12.44 -4.72,-8.55 C-4.72,-8.55 -12.78,7.57 -12.78,7.57 C-15,12.01 -10.42,16.78 -5.89,14.74 C-5.89,14.74 -2.17,13.07 -2.17,13.07 C-0.79,12.45 0.79,12.45 2.17,13.07 C2.17,13.07 5.9,14.74 5.9,14.74 C10.42,16.78 15,12.01 12.78,7.57c " />
+                </group>
+            </group>
+            <group android:name="time_group" />
+        </vector>
+    </aapt:attr>
+    <target android:name="_R_G_L_1_G_D_0_P_0">
+        <aapt:attr name="android:animation">
+            <set android:ordering="together">
+                <objectAnimator
+                    android:propertyName="pathData"
+                    android:duration="67"
+                    android:startOffset="0"
+                    android:valueFrom="M-0.56 -14.03 C3.65,-13.99 14.58,7.64 11.51,10.42 C8.45,13.2 5.92,9.56 -0.46,9.61 C-6.85,9.65 -9.27,12.76 -12.33,10.46 C-15.39,8.15 -4.77,-14.07 -0.56,-14.03c "
+                    android:valueTo="M-0.56 -14.03 C3.65,-13.99 14.58,7.64 11.51,10.42 C8.45,13.2 5.92,9.56 -0.46,9.61 C-6.85,9.65 -9.27,12.76 -12.33,10.46 C-15.39,8.15 -4.77,-14.07 -0.56,-14.03c "
+                    android:valueType="pathType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.3,0 0.8,1 1.0,1.0" />
+                    </aapt:attr>
+                </objectAnimator>
+                <objectAnimator
+                    android:propertyName="pathData"
+                    android:duration="283"
+                    android:startOffset="67"
+                    android:valueFrom="M-0.56 -14.03 C3.65,-13.99 14.58,7.64 11.51,10.42 C8.45,13.2 5.92,9.56 -0.46,9.61 C-6.85,9.65 -9.27,12.76 -12.33,10.46 C-15.39,8.15 -4.77,-14.07 -0.56,-14.03c "
+                    android:valueTo="M-0.44 -8.69 C3.98,-8.69 7.56,-5.11 7.56,-0.69 C7.56,3.73 3.98,7.31 -0.44,7.31 C-4.86,7.31 -8.44,3.73 -8.44,-0.69 C-8.44,-5.11 -4.86,-8.69 -0.44,-8.69c "
+                    android:valueType="pathType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.3,0 0.8,1 1.0,1.0" />
+                    </aapt:attr>
+                </objectAnimator>
+            </set>
+        </aapt:attr>
+    </target>
+    <target android:name="_R_G_L_1_G">
+        <aapt:attr name="android:animation">
+            <set android:ordering="together">
+                <objectAnimator
+                    android:propertyName="scaleY"
+                    android:duration="0"
+                    android:startOffset="67"
+                    android:valueFrom="0"
+                    android:valueTo="1"
+                    android:valueType="floatType" />
+            </set>
+        </aapt:attr>
+    </target>
+    <target android:name="_R_G_L_0_G_D_0_P_0">
+        <aapt:attr name="android:animation">
+            <set android:ordering="together">
+                <objectAnimator
+                    android:propertyName="pathData"
+                    android:duration="67"
+                    android:startOffset="0"
+                    android:valueFrom="M12.78 7.57 C12.78,7.57 4.72,-8.55 4.72,-8.55 C2.77,-12.44 -2.77,-12.44 -4.72,-8.55 C-4.72,-8.55 -12.78,7.57 -12.78,7.57 C-15,12.01 -10.42,16.78 -5.89,14.74 C-5.89,14.74 -2.17,13.07 -2.17,13.07 C-0.79,12.45 0.79,12.45 2.17,13.07 C2.17,13.07 5.9,14.74 5.9,14.74 C10.42,16.78 15,12.01 12.78,7.57c "
+                    android:valueTo="M12.78 7.57 C12.78,7.57 4.72,-8.55 4.72,-8.55 C2.77,-12.44 -2.77,-12.44 -4.72,-8.55 C-4.72,-8.55 -12.78,7.57 -12.78,7.57 C-15,12.01 -10.42,16.78 -5.89,14.74 C-5.89,14.74 -2.17,13.07 -2.17,13.07 C-0.79,12.45 0.79,12.45 2.17,13.07 C2.17,13.07 5.9,14.74 5.9,14.74 C10.42,16.78 15,12.01 12.78,7.57c "
+                    android:valueType="pathType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.3,0 0.833,1 1.0,1.0" />
+                    </aapt:attr>
+                </objectAnimator>
+                <objectAnimator
+                    android:propertyName="pathData"
+                    android:duration="283"
+                    android:startOffset="67"
+                    android:valueFrom="M12.78 7.57 C12.78,7.57 4.72,-8.55 4.72,-8.55 C2.77,-12.44 -2.77,-12.44 -4.72,-8.55 C-4.72,-8.55 -12.78,7.57 -12.78,7.57 C-15,12.01 -10.42,16.78 -5.89,14.74 C-5.89,14.74 -2.17,13.07 -2.17,13.07 C-0.79,12.45 0.79,12.45 2.17,13.07 C2.17,13.07 5.9,14.74 5.9,14.74 C10.42,16.78 15,12.01 12.78,7.57c "
+                    android:valueTo="M6.12 0.21 C6.12,0.21 2.27,-4.35 2.27,-4.35 C1.34,-6.2 -1.3,-6.2 -2.23,-4.35 C-2.23,-4.35 -6.06,0.21 -6.06,0.21 C-7.12,2.33 -5.46,5.79 -4.03,6.54 C-2.28,7.45 -1.01,7.48 -1.01,7.48 C-1.01,7.48 1.05,7.48 1.05,7.48 C1.05,7.48 3.28,7.36 4.23,6.66 C4.92,6.15 7.18,2.33 6.12,0.21c "
+                    android:valueType="pathType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.3,0 0.833,1 1.0,1.0" />
+                    </aapt:attr>
+                </objectAnimator>
+            </set>
+        </aapt:attr>
+    </target>
+    <target android:name="_R_G_L_0_G">
+        <aapt:attr name="android:animation">
+            <set android:ordering="together">
+                <objectAnimator
+                    android:propertyName="scaleX"
+                    android:duration="67"
+                    android:startOffset="0"
+                    android:valueFrom="0"
+                    android:valueTo="1"
+                    android:valueType="floatType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
+                    </aapt:attr>
+                </objectAnimator>
+                <objectAnimator
+                    android:propertyName="scaleY"
+                    android:duration="67"
+                    android:startOffset="0"
+                    android:valueFrom="0"
+                    android:valueTo="1"
+                    android:valueType="floatType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
+                    </aapt:attr>
+                </objectAnimator>
+            </set>
+        </aapt:attr>
+    </target>
+    <target android:name="time_group">
+        <aapt:attr name="android:animation">
+            <set android:ordering="together">
+                <objectAnimator
+                    android:propertyName="translateX"
+                    android:duration="500"
+                    android:startOffset="0"
+                    android:valueFrom="0"
+                    android:valueTo="1"
+                    android:valueType="floatType" />
+            </set>
+        </aapt:attr>
+    </target>
+</animated-vector>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/pin_dot_shape_3_avd.xml b/packages/SystemUI/res/drawable/pin_dot_shape_3_avd.xml
index 95b8044..78e2249 100644
--- a/packages/SystemUI/res/drawable/pin_dot_shape_3_avd.xml
+++ b/packages/SystemUI/res/drawable/pin_dot_shape_3_avd.xml
@@ -1 +1,141 @@
-<animated-vector xmlns:android="http://schemas.android.com/apk/res/android" xmlns:aapt="http://schemas.android.com/aapt"><aapt:attr name="android:drawable"><vector android:height="30dp" android:width="30dp" android:viewportHeight="30" android:viewportWidth="30"><group android:name="_R_G"><group android:name="_R_G_L_1_G" android:translateX="15.441" android:translateY="15.691" android:scaleY="0"><path android:name="_R_G_L_1_G_D_0_P_0" android:fillColor="#ffffff" android:fillAlpha="1" android:fillType="nonZero" android:pathData=" M-10.2 -11.16 C-1.58,-18.94 4.25,-12.72 8.06,-8.64 C11.88,-4.57 17.93,1.89 9.39,9.74 C0.85,17.6 -5.06,11.3 -8.87,7.22 C-12.69,3.14 -18.81,-3.39 -10.2,-11.16c "/></group><group android:name="_R_G_L_0_G" android:translateX="15" android:translateY="15" android:scaleX="0" android:scaleY="0"><path android:name="_R_G_L_0_G_D_0_P_0" android:fillColor="#ffffff" android:fillAlpha="1" android:fillType="nonZero" android:pathData=" M10.71 10.71 C5.92,15.5 -1.85,15.5 -6.64,10.71 C-6.64,10.71 -10.71,6.64 -10.71,6.64 C-15.5,1.85 -15.5,-5.92 -10.71,-10.71 C-5.92,-15.5 1.85,-15.5 6.64,-10.71 C6.64,-10.71 10.71,-6.64 10.71,-6.64 C15.5,-1.85 15.5,5.92 10.71,10.71c "/></group></group><group android:name="time_group"/></vector></aapt:attr><target android:name="_R_G_L_1_G_D_0_P_0"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="pathData" android:duration="67" android:startOffset="0" android:valueFrom="M-10.2 -11.16 C-1.58,-18.94 4.25,-12.72 8.06,-8.64 C11.88,-4.57 17.93,1.89 9.39,9.74 C0.85,17.6 -5.06,11.3 -8.87,7.22 C-12.69,3.14 -18.81,-3.39 -10.2,-11.16c " android:valueTo="M-10.2 -11.16 C-1.58,-18.94 4.25,-12.72 8.06,-8.64 C11.88,-4.57 17.93,1.89 9.39,9.74 C0.85,17.6 -5.06,11.3 -8.87,7.22 C-12.69,3.14 -18.81,-3.39 -10.2,-11.16c " android:valueType="pathType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.3,0 0.8,1 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="pathData" android:duration="283" android:startOffset="67" android:valueFrom="M-10.2 -11.16 C-1.58,-18.94 4.25,-12.72 8.06,-8.64 C11.88,-4.57 17.93,1.89 9.39,9.74 C0.85,17.6 -5.06,11.3 -8.87,7.22 C-12.69,3.14 -18.81,-3.39 -10.2,-11.16c " android:valueTo="M-0.44 -8.69 C3.98,-8.69 7.56,-5.11 7.56,-0.69 C7.56,3.73 3.98,7.31 -0.44,7.31 C-4.86,7.31 -8.44,3.73 -8.44,-0.69 C-8.44,-5.11 -4.86,-8.69 -0.44,-8.69c " android:valueType="pathType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.3,0 0.8,1 1.0,1.0"/></aapt:attr></objectAnimator></set></aapt:attr></target><target android:name="_R_G_L_1_G"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="scaleY" android:duration="0" android:startOffset="67" android:valueFrom="0" android:valueTo="1" android:valueType="floatType"/></set></aapt:attr></target><target android:name="_R_G_L_0_G"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="scaleX" android:duration="67" android:startOffset="0" android:valueFrom="0" android:valueTo="1" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="scaleY" android:duration="67" android:startOffset="0" android:valueFrom="0" android:valueTo="1" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="scaleX" android:duration="283" android:startOffset="67" android:valueFrom="1" android:valueTo="0.4" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.7,0 0.6,1 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="scaleY" android:duration="283" android:startOffset="67" android:valueFrom="1" android:valueTo="0.4" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.7,0 0.6,1 1.0,1.0"/></aapt:attr></objectAnimator></set></aapt:attr></target><target android:name="_R_G_L_0_G"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="scaleY" android:duration="0" android:startOffset="0" android:valueFrom="0" android:valueTo="1" android:valueType="floatType"/></set></aapt:attr></target><target android:name="_R_G_L_0_G"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="scaleY" android:duration="0" android:startOffset="367" android:valueFrom="1" android:valueTo="0" android:valueType="floatType"/></set></aapt:attr></target><target android:name="time_group"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="translateX" android:duration="500" android:startOffset="0" android:valueFrom="0" android:valueTo="1" android:valueType="floatType"/></set></aapt:attr></target></animated-vector>
\ No newline at end of file
+<animated-vector xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:aapt="http://schemas.android.com/aapt">
+    <aapt:attr name="android:drawable">
+        <vector
+            android:height="30dp"
+            android:width="30dp"
+            android:viewportHeight="30"
+            android:viewportWidth="30">
+            <group android:name="_R_G">
+                <group
+                    android:name="_R_G_L_1_G"
+                    android:translateX="15.441"
+                    android:translateY="15.691"
+                    android:scaleY="0">
+                    <path
+                        android:name="_R_G_L_1_G_D_0_P_0"
+                        android:fillColor="#ffffff"
+                        android:fillAlpha="1"
+                        android:fillType="nonZero"
+                        android:pathData=" M-10.2 -11.16 C-1.58,-18.94 4.25,-12.72 8.06,-8.64 C11.88,-4.57 17.93,1.89 9.39,9.74 C0.85,17.6 -5.06,11.3 -8.87,7.22 C-12.69,3.14 -18.81,-3.39 -10.2,-11.16c " />
+                </group>
+                <group
+                    android:name="_R_G_L_0_G"
+                    android:translateX="15"
+                    android:translateY="15">
+                    <path
+                        android:name="_R_G_L_0_G_D_0_P_0"
+                        android:fillColor="#ffffff"
+                        android:fillAlpha="1"
+                        android:fillType="nonZero"
+                        android:pathData=" M10.71 10.71 C5.92,15.5 -1.85,15.5 -6.64,10.71 C-6.64,10.71 -10.71,6.64 -10.71,6.64 C-15.5,1.85 -15.5,-5.92 -10.71,-10.71 C-5.92,-15.5 1.85,-15.5 6.64,-10.71 C6.64,-10.71 10.71,-6.64 10.71,-6.64 C15.5,-1.85 15.5,5.92 10.71,10.71c " />
+                </group>
+            </group>
+            <group android:name="time_group" />
+        </vector>
+    </aapt:attr>
+    <target android:name="_R_G_L_1_G_D_0_P_0">
+        <aapt:attr name="android:animation">
+            <set android:ordering="together">
+                <objectAnimator
+                    android:propertyName="pathData"
+                    android:duration="67"
+                    android:startOffset="0"
+                    android:valueFrom="M-10.2 -11.16 C-1.58,-18.94 4.25,-12.72 8.06,-8.64 C11.88,-4.57 17.93,1.89 9.39,9.74 C0.85,17.6 -5.06,11.3 -8.87,7.22 C-12.69,3.14 -18.81,-3.39 -10.2,-11.16c "
+                    android:valueTo="M-10.2 -11.16 C-1.58,-18.94 4.25,-12.72 8.06,-8.64 C11.88,-4.57 17.93,1.89 9.39,9.74 C0.85,17.6 -5.06,11.3 -8.87,7.22 C-12.69,3.14 -18.81,-3.39 -10.2,-11.16c "
+                    android:valueType="pathType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.3,0 0.8,1 1.0,1.0" />
+                    </aapt:attr>
+                </objectAnimator>
+                <objectAnimator
+                    android:propertyName="pathData"
+                    android:duration="283"
+                    android:startOffset="67"
+                    android:valueFrom="M-10.2 -11.16 C-1.58,-18.94 4.25,-12.72 8.06,-8.64 C11.88,-4.57 17.93,1.89 9.39,9.74 C0.85,17.6 -5.06,11.3 -8.87,7.22 C-12.69,3.14 -18.81,-3.39 -10.2,-11.16c "
+                    android:valueTo="M-0.44 -8.69 C3.98,-8.69 7.56,-5.11 7.56,-0.69 C7.56,3.73 3.98,7.31 -0.44,7.31 C-4.86,7.31 -8.44,3.73 -8.44,-0.69 C-8.44,-5.11 -4.86,-8.69 -0.44,-8.69c "
+                    android:valueType="pathType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.3,0 0.8,1 1.0,1.0" />
+                    </aapt:attr>
+                </objectAnimator>
+            </set>
+        </aapt:attr>
+    </target>
+    <target android:name="_R_G_L_1_G">
+        <aapt:attr name="android:animation">
+            <set android:ordering="together">
+                <objectAnimator
+                    android:propertyName="scaleY"
+                    android:duration="0"
+                    android:startOffset="67"
+                    android:valueFrom="0"
+                    android:valueTo="1"
+                    android:valueType="floatType" />
+            </set>
+        </aapt:attr>
+    </target>
+    <target android:name="_R_G_L_0_G">
+        <aapt:attr name="android:animation">
+            <set android:ordering="together">
+                <objectAnimator
+                    android:propertyName="scaleX"
+                    android:duration="67"
+                    android:startOffset="0"
+                    android:valueFrom="0"
+                    android:valueTo="1"
+                    android:valueType="floatType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
+                    </aapt:attr>
+                </objectAnimator>
+                <objectAnimator
+                    android:propertyName="scaleY"
+                    android:duration="67"
+                    android:startOffset="0"
+                    android:valueFrom="0"
+                    android:valueTo="1"
+                    android:valueType="floatType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
+                    </aapt:attr>
+                </objectAnimator>
+                <objectAnimator
+                    android:propertyName="scaleX"
+                    android:duration="283"
+                    android:startOffset="67"
+                    android:valueFrom="1"
+                    android:valueTo="0.4"
+                    android:valueType="floatType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.7,0 0.6,1 1.0,1.0" />
+                    </aapt:attr>
+                </objectAnimator>
+                <objectAnimator
+                    android:propertyName="scaleY"
+                    android:duration="283"
+                    android:startOffset="67"
+                    android:valueFrom="1"
+                    android:valueTo="0.4"
+                    android:valueType="floatType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.7,0 0.6,1 1.0,1.0" />
+                    </aapt:attr>
+                </objectAnimator>
+            </set>
+        </aapt:attr>
+    </target>
+    <target android:name="time_group">
+        <aapt:attr name="android:animation">
+            <set android:ordering="together">
+                <objectAnimator
+                    android:propertyName="translateX"
+                    android:duration="500"
+                    android:startOffset="0"
+                    android:valueFrom="0"
+                    android:valueTo="1"
+                    android:valueType="floatType" />
+            </set>
+        </aapt:attr>
+    </target>
+</animated-vector>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/pin_dot_shape_4_avd.xml b/packages/SystemUI/res/drawable/pin_dot_shape_4_avd.xml
index 8ea8f85..35c7210 100644
--- a/packages/SystemUI/res/drawable/pin_dot_shape_4_avd.xml
+++ b/packages/SystemUI/res/drawable/pin_dot_shape_4_avd.xml
@@ -1 +1,119 @@
-<animated-vector xmlns:android="http://schemas.android.com/apk/res/android" xmlns:aapt="http://schemas.android.com/aapt"><aapt:attr name="android:drawable"><vector android:height="30dp" android:width="30dp" android:viewportHeight="30" android:viewportWidth="30"><group android:name="_R_G"><group android:name="_R_G_L_1_G" android:translateX="15.441" android:translateY="15.691" android:scaleY="0"><path android:name="_R_G_L_1_G_D_0_P_0" android:fillColor="#ffffff" android:fillAlpha="1" android:fillType="nonZero" android:pathData=" M-0.44 -8.69 C3.97,-8.69 7.56,-5.11 7.56,-0.69 C7.56,3.72 3.97,7.31 -0.44,7.31 C-4.86,7.31 -8.44,3.72 -8.44,-0.69 C-8.44,-5.11 -4.86,-8.69 -0.44,-8.69c "/></group><group android:name="_R_G_L_0_G" android:translateX="15" android:translateY="15" android:scaleX="0" android:scaleY="0"><path android:name="_R_G_L_0_G_D_0_P_0" android:fillColor="#ffffff" android:fillAlpha="1" android:fillType="nonZero" android:pathData=" M8 0 C8,-1.35 7.97,-1.94 7.41,-3.02 C6.85,-4.09 6.61,-4.79 5.66,-5.66 C4.7,-6.52 4.2,-6.97 3.15,-7.36 C2.09,-7.74 1.39,-8 0,-8 C-1.39,-8 -2.18,-7.78 -3.12,-7.37 C-4.07,-6.96 -4.67,-6.63 -5.66,-5.66 C-6.64,-4.68 -6.98,-4.1 -7.37,-3.13 C-7.78,-2.08 -8,-1.39 -8,0 C-8,1.4 -7.86,1.98 -7.47,2.87 C-7.08,3.76 -6.68,4.66 -5.66,5.66 C-4.63,6.65 -4,6.96 -3.12,7.37 C-2.25,7.78 -1.32,8 0,8 C1.32,8 1.86,7.88 2.9,7.46 C3.95,7.03 4.85,6.63 5.66,5.66 C6.46,4.69 6.78,4.45 7.29,3.29 C7.81,2.14 8,1.35 8,0c "/></group></group><group android:name="time_group"/></vector></aapt:attr><target android:name="_R_G_L_1_G"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="scaleY" android:duration="0" android:startOffset="67" android:valueFrom="0" android:valueTo="1" android:valueType="floatType"/></set></aapt:attr></target><target android:name="_R_G_L_0_G_D_0_P_0"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="pathData" android:duration="67" android:startOffset="0" android:valueFrom="M8 0 C8,-1.35 7.97,-1.94 7.41,-3.02 C6.85,-4.09 6.61,-4.79 5.66,-5.66 C4.7,-6.52 4.2,-6.97 3.15,-7.36 C2.09,-7.74 1.39,-8 0,-8 C-1.39,-8 -2.18,-7.78 -3.12,-7.37 C-4.07,-6.96 -4.67,-6.63 -5.66,-5.66 C-6.64,-4.68 -6.98,-4.1 -7.37,-3.13 C-7.78,-2.08 -8,-1.39 -8,0 C-8,1.4 -7.86,1.98 -7.47,2.87 C-7.08,3.76 -6.68,4.66 -5.66,5.66 C-4.63,6.65 -4,6.96 -3.12,7.37 C-2.25,7.78 -1.32,8 0,8 C1.32,8 1.86,7.88 2.9,7.46 C3.95,7.03 4.85,6.63 5.66,5.66 C6.46,4.69 6.78,4.45 7.29,3.29 C7.81,2.14 8,1.35 8,0c " android:valueTo="M15 0 C15,-2.52 11.99,-2.3 10.94,-4.32 C9.89,-6.33 12.4,-8.98 10.61,-10.61 C8.82,-12.23 6.44,-10.15 4.46,-10.86 C2.48,-11.58 2.61,-15 0,-15 C-2.61,-15 -2.68,-11.61 -4.46,-10.84 C-6.23,-10.08 -8.76,-12.44 -10.61,-10.61 C-12.45,-8.78 -10.31,-6.69 -10.87,-4.64 C-11.43,-2.61 -15,-2.61 -15,0 C-15,2.62 -11.67,2.75 -10.94,4.42 C-10.21,6.08 -12.53,8.74 -10.61,10.61 C-8.68,12.47 -6.18,10.19 -4.54,10.96 C-2.89,11.72 -2.48,15 0,15 C2.48,15 2.49,11.78 4.45,10.99 C6.4,10.19 9.09,12.43 10.61,10.61 C12.12,8.79 10,6.52 10.97,4.36 C11.94,2.2 15,2.52 15,0c " android:valueType="pathType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.167,0 0.833,1 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="pathData" android:duration="283" android:startOffset="67" android:valueFrom="M15 0 C15,-2.52 11.99,-2.3 10.94,-4.32 C9.89,-6.33 12.4,-8.98 10.61,-10.61 C8.82,-12.23 6.44,-10.15 4.46,-10.86 C2.48,-11.58 2.61,-15 0,-15 C-2.61,-15 -2.68,-11.61 -4.46,-10.84 C-6.23,-10.08 -8.76,-12.44 -10.61,-10.61 C-12.45,-8.78 -10.31,-6.69 -10.87,-4.64 C-11.43,-2.61 -15,-2.61 -15,0 C-15,2.62 -11.67,2.75 -10.94,4.42 C-10.21,6.08 -12.53,8.74 -10.61,10.61 C-8.68,12.47 -6.18,10.19 -4.54,10.96 C-2.89,11.72 -2.48,15 0,15 C2.48,15 2.49,11.78 4.45,10.99 C6.4,10.19 9.09,12.43 10.61,10.61 C12.12,8.79 10,6.52 10.97,4.36 C11.94,2.2 15,2.52 15,0c " android:valueTo="M7.73 0 C7.73,-1.3 7.71,-1.88 7.16,-2.92 C6.62,-3.95 6.39,-4.63 5.47,-5.47 C4.55,-6.31 4.06,-6.74 3.04,-7.11 C2.02,-7.48 1.35,-7.73 0,-7.73 C-1.34,-7.73 -2.1,-7.52 -3.02,-7.12 C-3.93,-6.73 -4.52,-6.41 -5.47,-5.47 C-6.42,-4.53 -6.75,-3.96 -7.12,-3.02 C-7.52,-2.01 -7.73,-1.35 -7.73,0 C-7.73,1.35 -7.6,1.91 -7.22,2.77 C-6.85,3.63 -6.46,4.51 -5.47,5.47 C-4.48,6.43 -3.87,6.73 -3.02,7.12 C-2.17,7.52 -1.28,7.73 0,7.73 C1.28,7.73 1.8,7.62 2.81,7.21 C3.81,6.8 4.69,6.41 5.47,5.47 C6.25,4.53 6.55,4.3 7.05,3.19 C7.55,2.07 7.73,1.3 7.73,0c " android:valueType="pathType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.3,0 0.7,1 1.0,1.0"/></aapt:attr></objectAnimator></set></aapt:attr></target><target android:name="_R_G_L_0_G"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="scaleX" android:duration="67" android:startOffset="0" android:valueFrom="0" android:valueTo="1" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="scaleY" android:duration="67" android:startOffset="0" android:valueFrom="0" android:valueTo="1" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0"/></aapt:attr></objectAnimator></set></aapt:attr></target><target android:name="_R_G_L_0_G"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="scaleY" android:duration="0" android:startOffset="0" android:valueFrom="0" android:valueTo="1" android:valueType="floatType"/></set></aapt:attr></target><target android:name="_R_G_L_0_G"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="scaleY" android:duration="0" android:startOffset="367" android:valueFrom="1" android:valueTo="0" android:valueType="floatType"/></set></aapt:attr></target><target android:name="time_group"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="translateX" android:duration="500" android:startOffset="0" android:valueFrom="0" android:valueTo="1" android:valueType="floatType"/></set></aapt:attr></target></animated-vector>
\ No newline at end of file
+<animated-vector xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:aapt="http://schemas.android.com/aapt">
+    <target android:name="_R_G_L_1_G">
+        <aapt:attr name="android:animation">
+            <set android:ordering="together">
+                <objectAnimator
+                    android:duration="0"
+                    android:propertyName="scaleY"
+                    android:startOffset="67"
+                    android:valueFrom="0"
+                    android:valueTo="1"
+                    android:valueType="floatType" />
+            </set>
+        </aapt:attr>
+    </target>
+    <target android:name="_R_G_L_0_G_D_0_P_0">
+        <aapt:attr name="android:animation">
+            <set android:ordering="together">
+                <objectAnimator
+                    android:duration="67"
+                    android:propertyName="pathData"
+                    android:startOffset="0"
+                    android:valueFrom="M8 0 C8,-1.35 7.97,-1.94 7.41,-3.02 C6.85,-4.09 6.61,-4.79 5.66,-5.66 C4.7,-6.52 4.2,-6.97 3.15,-7.36 C2.09,-7.74 1.39,-8 0,-8 C-1.39,-8 -2.18,-7.78 -3.12,-7.37 C-4.07,-6.96 -4.67,-6.63 -5.66,-5.66 C-6.64,-4.68 -6.98,-4.1 -7.37,-3.13 C-7.78,-2.08 -8,-1.39 -8,0 C-8,1.4 -7.86,1.98 -7.47,2.87 C-7.08,3.76 -6.68,4.66 -5.66,5.66 C-4.63,6.65 -4,6.96 -3.12,7.37 C-2.25,7.78 -1.32,8 0,8 C1.32,8 1.86,7.88 2.9,7.46 C3.95,7.03 4.85,6.63 5.66,5.66 C6.46,4.69 6.78,4.45 7.29,3.29 C7.81,2.14 8,1.35 8,0c "
+                    android:valueTo="M15 0 C15,-2.52 11.99,-2.3 10.94,-4.32 C9.89,-6.33 12.4,-8.98 10.61,-10.61 C8.82,-12.23 6.44,-10.15 4.46,-10.86 C2.48,-11.58 2.61,-15 0,-15 C-2.61,-15 -2.68,-11.61 -4.46,-10.84 C-6.23,-10.08 -8.76,-12.44 -10.61,-10.61 C-12.45,-8.78 -10.31,-6.69 -10.87,-4.64 C-11.43,-2.61 -15,-2.61 -15,0 C-15,2.62 -11.67,2.75 -10.94,4.42 C-10.21,6.08 -12.53,8.74 -10.61,10.61 C-8.68,12.47 -6.18,10.19 -4.54,10.96 C-2.89,11.72 -2.48,15 0,15 C2.48,15 2.49,11.78 4.45,10.99 C6.4,10.19 9.09,12.43 10.61,10.61 C12.12,8.79 10,6.52 10.97,4.36 C11.94,2.2 15,2.52 15,0c "
+                    android:valueType="pathType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0 0.833,1 1.0,1.0" />
+                    </aapt:attr>
+                </objectAnimator>
+                <objectAnimator
+                    android:duration="283"
+                    android:propertyName="pathData"
+                    android:startOffset="67"
+                    android:valueFrom="M15 0 C15,-2.52 11.99,-2.3 10.94,-4.32 C9.89,-6.33 12.4,-8.98 10.61,-10.61 C8.82,-12.23 6.44,-10.15 4.46,-10.86 C2.48,-11.58 2.61,-15 0,-15 C-2.61,-15 -2.68,-11.61 -4.46,-10.84 C-6.23,-10.08 -8.76,-12.44 -10.61,-10.61 C-12.45,-8.78 -10.31,-6.69 -10.87,-4.64 C-11.43,-2.61 -15,-2.61 -15,0 C-15,2.62 -11.67,2.75 -10.94,4.42 C-10.21,6.08 -12.53,8.74 -10.61,10.61 C-8.68,12.47 -6.18,10.19 -4.54,10.96 C-2.89,11.72 -2.48,15 0,15 C2.48,15 2.49,11.78 4.45,10.99 C6.4,10.19 9.09,12.43 10.61,10.61 C12.12,8.79 10,6.52 10.97,4.36 C11.94,2.2 15,2.52 15,0c "
+                    android:valueTo="M7.73 0 C7.73,-1.3 7.71,-1.88 7.16,-2.92 C6.62,-3.95 6.39,-4.63 5.47,-5.47 C4.55,-6.31 4.06,-6.74 3.04,-7.11 C2.02,-7.48 1.35,-7.73 0,-7.73 C-1.34,-7.73 -2.1,-7.52 -3.02,-7.12 C-3.93,-6.73 -4.52,-6.41 -5.47,-5.47 C-6.42,-4.53 -6.75,-3.96 -7.12,-3.02 C-7.52,-2.01 -7.73,-1.35 -7.73,0 C-7.73,1.35 -7.6,1.91 -7.22,2.77 C-6.85,3.63 -6.46,4.51 -5.47,5.47 C-4.48,6.43 -3.87,6.73 -3.02,7.12 C-2.17,7.52 -1.28,7.73 0,7.73 C1.28,7.73 1.8,7.62 2.81,7.21 C3.81,6.8 4.69,6.41 5.47,5.47 C6.25,4.53 6.55,4.3 7.05,3.19 C7.55,2.07 7.73,1.3 7.73,0c "
+                    android:valueType="pathType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.3,0 0.7,1 1.0,1.0" />
+                    </aapt:attr>
+                </objectAnimator>
+            </set>
+        </aapt:attr>
+    </target>
+    <target android:name="_R_G_L_0_G">
+        <aapt:attr name="android:animation">
+            <set android:ordering="together">
+                <objectAnimator
+                    android:duration="67"
+                    android:propertyName="scaleX"
+                    android:startOffset="0"
+                    android:valueFrom="0"
+                    android:valueTo="1"
+                    android:valueType="floatType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
+                    </aapt:attr>
+                </objectAnimator>
+                <objectAnimator
+                    android:duration="67"
+                    android:propertyName="scaleY"
+                    android:startOffset="0"
+                    android:valueFrom="0"
+                    android:valueTo="1"
+                    android:valueType="floatType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
+                    </aapt:attr>
+                </objectAnimator>
+            </set>
+        </aapt:attr>
+    </target>
+    <target android:name="time_group">
+        <aapt:attr name="android:animation">
+            <set android:ordering="together">
+                <objectAnimator
+                    android:duration="500"
+                    android:propertyName="translateX"
+                    android:startOffset="0"
+                    android:valueFrom="0"
+                    android:valueTo="1"
+                    android:valueType="floatType" />
+            </set>
+        </aapt:attr>
+    </target>
+    <aapt:attr name="android:drawable">
+        <vector
+            android:width="30dp"
+            android:height="30dp"
+            android:viewportHeight="30"
+            android:viewportWidth="30">
+            <group android:name="_R_G">
+                <group
+                    android:name="_R_G_L_1_G"
+                    android:scaleY="0"
+                    android:translateX="15.441"
+                    android:translateY="15.691">
+                    <path
+                        android:name="_R_G_L_1_G_D_0_P_0"
+                        android:fillAlpha="1"
+                        android:fillColor="#ffffff"
+                        android:fillType="nonZero"
+                        android:pathData=" M-0.44 -8.69 C3.97,-8.69 7.56,-5.11 7.56,-0.69 C7.56,3.72 3.97,7.31 -0.44,7.31 C-4.86,7.31 -8.44,3.72 -8.44,-0.69 C-8.44,-5.11 -4.86,-8.69 -0.44,-8.69c " />
+                </group>
+                <group
+                    android:name="_R_G_L_0_G"
+                    android:translateX="15"
+                    android:translateY="15">
+                    <path
+                        android:name="_R_G_L_0_G_D_0_P_0"
+                        android:fillAlpha="1"
+                        android:fillColor="#ffffff"
+                        android:fillType="nonZero"
+                        android:pathData=" M8 0 C8,-1.35 7.97,-1.94 7.41,-3.02 C6.85,-4.09 6.61,-4.79 5.66,-5.66 C4.7,-6.52 4.2,-6.97 3.15,-7.36 C2.09,-7.74 1.39,-8 0,-8 C-1.39,-8 -2.18,-7.78 -3.12,-7.37 C-4.07,-6.96 -4.67,-6.63 -5.66,-5.66 C-6.64,-4.68 -6.98,-4.1 -7.37,-3.13 C-7.78,-2.08 -8,-1.39 -8,0 C-8,1.4 -7.86,1.98 -7.47,2.87 C-7.08,3.76 -6.68,4.66 -5.66,5.66 C-4.63,6.65 -4,6.96 -3.12,7.37 C-2.25,7.78 -1.32,8 0,8 C1.32,8 1.86,7.88 2.9,7.46 C3.95,7.03 4.85,6.63 5.66,5.66 C6.46,4.69 6.78,4.45 7.29,3.29 C7.81,2.14 8,1.35 8,0c " />
+                </group>
+            </group>
+            <group android:name="time_group" />
+        </vector>
+    </aapt:attr>
+</animated-vector>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/pin_dot_shape_5_avd.xml b/packages/SystemUI/res/drawable/pin_dot_shape_5_avd.xml
index 3779c80..9458b2e 100644
--- a/packages/SystemUI/res/drawable/pin_dot_shape_5_avd.xml
+++ b/packages/SystemUI/res/drawable/pin_dot_shape_5_avd.xml
@@ -1 +1,148 @@
-<animated-vector xmlns:android="http://schemas.android.com/apk/res/android" xmlns:aapt="http://schemas.android.com/aapt"><aapt:attr name="android:drawable"><vector android:height="30dp" android:width="30dp" android:viewportHeight="30" android:viewportWidth="30"><group android:name="_R_G"><group android:name="_R_G_L_1_G" android:translateX="15.397" android:translateY="15.691" android:scaleY="0"><path android:name="_R_G_L_1_G_D_0_P_0" android:fillColor="#ffffff" android:fillAlpha="1" android:fillType="nonZero" android:pathData=" M-0.48 -12.86 C2.96,-12.87 14.59,5.93 11.98,9.12 C9.42,12.26 5.76,11.36 -0.48,11.41 C-6.72,11.45 -10.24,11.91 -12.78,9.16 C-15.4,6.32 -3.91,-12.85 -0.48,-12.86c "/></group><group android:name="_R_G_L_0_G" android:translateX="15" android:translateY="17.64" android:pivotY="-2.64" android:scaleX="0" android:scaleY="0"><path android:name="_R_G_L_0_G_D_0_P_0" android:fillColor="#ffffff" android:fillAlpha="1" android:fillType="nonZero" android:pathData=" M-4.68 -13.34 C-2.59,-17.01 2.64,-17 4.72,-13.33 C4.72,-13.33 13.65,2.45 13.65,2.45 C15.72,6.11 13.11,10.66 8.94,10.65 C8.94,10.65 -8.98,10.62 -8.98,10.62 C-13.15,10.61 -15.75,6.05 -13.67,2.4 C-13.67,2.4 -4.68,-13.34 -4.68,-13.34c "/></group></group><group android:name="time_group"/></vector></aapt:attr><target android:name="_R_G_L_1_G_D_0_P_0"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="pathData" android:duration="67" android:startOffset="0" android:valueFrom="M-0.48 -12.86 C2.96,-12.87 14.59,5.93 11.98,9.12 C9.42,12.26 5.76,11.36 -0.48,11.41 C-6.72,11.45 -10.24,11.91 -12.78,9.16 C-15.4,6.32 -3.91,-12.85 -0.48,-12.86c " android:valueTo="M-0.48 -12.86 C2.96,-12.87 14.59,5.93 11.98,9.12 C9.42,12.26 5.76,11.36 -0.48,11.41 C-6.72,11.45 -10.24,11.91 -12.78,9.16 C-15.4,6.32 -3.91,-12.85 -0.48,-12.86c " android:valueType="pathType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.3,0 0.7,1 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="pathData" android:duration="283" android:startOffset="67" android:valueFrom="M-0.48 -12.86 C2.96,-12.87 14.59,5.93 11.98,9.12 C9.42,12.26 5.76,11.36 -0.48,11.41 C-6.72,11.45 -10.24,11.91 -12.78,9.16 C-15.4,6.32 -3.91,-12.85 -0.48,-12.86c " android:valueTo="M-0.44 -8.69 C3.98,-8.69 7.56,-5.11 7.56,-0.69 C7.56,3.73 3.98,7.31 -0.44,7.31 C-4.86,7.31 -8.44,3.73 -8.44,-0.69 C-8.44,-5.11 -4.86,-8.69 -0.44,-8.69c " android:valueType="pathType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.3,0 0.7,1 1.0,1.0"/></aapt:attr></objectAnimator></set></aapt:attr></target><target android:name="_R_G_L_1_G"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="scaleY" android:duration="0" android:startOffset="67" android:valueFrom="0" android:valueTo="1" android:valueType="floatType"/></set></aapt:attr></target><target android:name="_R_G_L_0_G_D_0_P_0"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="pathData" android:duration="67" android:startOffset="0" android:valueFrom="M-4.68 -13.34 C-2.59,-17.01 2.64,-17 4.72,-13.33 C4.72,-13.33 13.65,2.45 13.65,2.45 C15.72,6.11 13.11,10.66 8.94,10.65 C8.94,10.65 -8.98,10.62 -8.98,10.62 C-13.15,10.61 -15.75,6.05 -13.67,2.4 C-13.67,2.4 -4.68,-13.34 -4.68,-13.34c " android:valueTo="M-4.68 -13.34 C-2.59,-17.01 2.64,-17 4.72,-13.33 C4.72,-13.33 13.65,2.45 13.65,2.45 C15.72,6.11 13.11,10.66 8.94,10.65 C8.94,10.65 -8.98,10.62 -8.98,10.62 C-13.15,10.61 -15.75,6.05 -13.67,2.4 C-13.67,2.4 -4.68,-13.34 -4.68,-13.34c " android:valueType="pathType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.3,0 0.7,1 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="pathData" android:duration="283" android:startOffset="67" android:valueFrom="M-4.68 -13.34 C-2.59,-17.01 2.64,-17 4.72,-13.33 C4.72,-13.33 13.65,2.45 13.65,2.45 C15.72,6.11 13.11,10.66 8.94,10.65 C8.94,10.65 -8.98,10.62 -8.98,10.62 C-13.15,10.61 -15.75,6.05 -13.67,2.4 C-13.67,2.4 -4.68,-13.34 -4.68,-13.34c " android:valueTo="M-2.99 -8.52 C-1.33,-9.83 1.52,-9.34 3.25,-8.28 C4.98,-7.22 6.77,-5.88 6.7,-2.4 C6.64,1.08 4.48,2.26 3.2,3.05 C1.92,3.83 -1.1,3.72 -3.03,2.76 C-4.97,1.79 -6.38,0.3 -6.37,-2.59 C-6.36,-5.49 -4.65,-7.2 -2.99,-8.52c " android:valueType="pathType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.3,0 0.7,1 1.0,1.0"/></aapt:attr></objectAnimator></set></aapt:attr></target><target android:name="_R_G_L_0_G"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="scaleX" android:duration="67" android:startOffset="0" android:valueFrom="0" android:valueTo="1" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="scaleY" android:duration="67" android:startOffset="0" android:valueFrom="0" android:valueTo="1" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0"/></aapt:attr></objectAnimator></set></aapt:attr></target><target android:name="_R_G_L_0_G"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="scaleY" android:duration="0" android:startOffset="0" android:valueFrom="0" android:valueTo="1" android:valueType="floatType"/></set></aapt:attr></target><target android:name="_R_G_L_0_G"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="scaleY" android:duration="0" android:startOffset="367" android:valueFrom="1" android:valueTo="0" android:valueType="floatType"/></set></aapt:attr></target><target android:name="time_group"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="translateX" android:duration="500" android:startOffset="0" android:valueFrom="0" android:valueTo="1" android:valueType="floatType"/></set></aapt:attr></target></animated-vector>
\ No newline at end of file
+<animated-vector xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:aapt="http://schemas.android.com/aapt">
+    <aapt:attr name="android:drawable">
+        <vector
+            android:height="30dp"
+            android:width="30dp"
+            android:viewportHeight="30"
+            android:viewportWidth="30">
+            <group android:name="_R_G">
+                <group
+                    android:name="_R_G_L_1_G"
+                    android:translateX="15.397"
+                    android:translateY="15.691"
+                    android:scaleY="0">
+                    <path
+                        android:name="_R_G_L_1_G_D_0_P_0"
+                        android:fillColor="#ffffff"
+                        android:fillAlpha="1"
+                        android:fillType="nonZero"
+                        android:pathData=" M-0.48 -12.86 C2.96,-12.87 14.59,5.93 11.98,9.12 C9.42,12.26 5.76,11.36 -0.48,11.41 C-6.72,11.45 -10.24,11.91 -12.78,9.16 C-15.4,6.32 -3.91,-12.85 -0.48,-12.86c " />
+                </group>
+                <group
+                    android:name="_R_G_L_0_G"
+                    android:translateX="15"
+                    android:translateY="17.64"
+                    android:pivotY="-2.64">
+                    <path
+                        android:name="_R_G_L_0_G_D_0_P_0"
+                        android:fillColor="#ffffff"
+                        android:fillAlpha="1"
+                        android:fillType="nonZero"
+                        android:pathData=" M-4.68 -13.34 C-2.59,-17.01 2.64,-17 4.72,-13.33 C4.72,-13.33 13.65,2.45 13.65,2.45 C15.72,6.11 13.11,10.66 8.94,10.65 C8.94,10.65 -8.98,10.62 -8.98,10.62 C-13.15,10.61 -15.75,6.05 -13.67,2.4 C-13.67,2.4 -4.68,-13.34 -4.68,-13.34c " />
+                </group>
+            </group>
+            <group android:name="time_group" />
+        </vector>
+    </aapt:attr>
+    <target android:name="_R_G_L_1_G_D_0_P_0">
+        <aapt:attr name="android:animation">
+            <set android:ordering="together">
+                <objectAnimator
+                    android:propertyName="pathData"
+                    android:duration="67"
+                    android:startOffset="0"
+                    android:valueFrom="M-0.48 -12.86 C2.96,-12.87 14.59,5.93 11.98,9.12 C9.42,12.26 5.76,11.36 -0.48,11.41 C-6.72,11.45 -10.24,11.91 -12.78,9.16 C-15.4,6.32 -3.91,-12.85 -0.48,-12.86c "
+                    android:valueTo="M-0.48 -12.86 C2.96,-12.87 14.59,5.93 11.98,9.12 C9.42,12.26 5.76,11.36 -0.48,11.41 C-6.72,11.45 -10.24,11.91 -12.78,9.16 C-15.4,6.32 -3.91,-12.85 -0.48,-12.86c "
+                    android:valueType="pathType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.3,0 0.7,1 1.0,1.0" />
+                    </aapt:attr>
+                </objectAnimator>
+                <objectAnimator
+                    android:propertyName="pathData"
+                    android:duration="283"
+                    android:startOffset="67"
+                    android:valueFrom="M-0.48 -12.86 C2.96,-12.87 14.59,5.93 11.98,9.12 C9.42,12.26 5.76,11.36 -0.48,11.41 C-6.72,11.45 -10.24,11.91 -12.78,9.16 C-15.4,6.32 -3.91,-12.85 -0.48,-12.86c "
+                    android:valueTo="M-0.44 -8.69 C3.98,-8.69 7.56,-5.11 7.56,-0.69 C7.56,3.73 3.98,7.31 -0.44,7.31 C-4.86,7.31 -8.44,3.73 -8.44,-0.69 C-8.44,-5.11 -4.86,-8.69 -0.44,-8.69c "
+                    android:valueType="pathType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.3,0 0.7,1 1.0,1.0" />
+                    </aapt:attr>
+                </objectAnimator>
+            </set>
+        </aapt:attr>
+    </target>
+    <target android:name="_R_G_L_1_G">
+        <aapt:attr name="android:animation">
+            <set android:ordering="together">
+                <objectAnimator
+                    android:propertyName="scaleY"
+                    android:duration="0"
+                    android:startOffset="67"
+                    android:valueFrom="0"
+                    android:valueTo="1"
+                    android:valueType="floatType" />
+            </set>
+        </aapt:attr>
+    </target>
+    <target android:name="_R_G_L_0_G_D_0_P_0">
+        <aapt:attr name="android:animation">
+            <set android:ordering="together">
+                <objectAnimator
+                    android:propertyName="pathData"
+                    android:duration="67"
+                    android:startOffset="0"
+                    android:valueFrom="M-4.68 -13.34 C-2.59,-17.01 2.64,-17 4.72,-13.33 C4.72,-13.33 13.65,2.45 13.65,2.45 C15.72,6.11 13.11,10.66 8.94,10.65 C8.94,10.65 -8.98,10.62 -8.98,10.62 C-13.15,10.61 -15.75,6.05 -13.67,2.4 C-13.67,2.4 -4.68,-13.34 -4.68,-13.34c "
+                    android:valueTo="M-4.68 -13.34 C-2.59,-17.01 2.64,-17 4.72,-13.33 C4.72,-13.33 13.65,2.45 13.65,2.45 C15.72,6.11 13.11,10.66 8.94,10.65 C8.94,10.65 -8.98,10.62 -8.98,10.62 C-13.15,10.61 -15.75,6.05 -13.67,2.4 C-13.67,2.4 -4.68,-13.34 -4.68,-13.34c "
+                    android:valueType="pathType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.3,0 0.7,1 1.0,1.0" />
+                    </aapt:attr>
+                </objectAnimator>
+                <objectAnimator
+                    android:propertyName="pathData"
+                    android:duration="283"
+                    android:startOffset="67"
+                    android:valueFrom="M-4.68 -13.34 C-2.59,-17.01 2.64,-17 4.72,-13.33 C4.72,-13.33 13.65,2.45 13.65,2.45 C15.72,6.11 13.11,10.66 8.94,10.65 C8.94,10.65 -8.98,10.62 -8.98,10.62 C-13.15,10.61 -15.75,6.05 -13.67,2.4 C-13.67,2.4 -4.68,-13.34 -4.68,-13.34c "
+                    android:valueTo="M-2.99 -8.52 C-1.33,-9.83 1.52,-9.34 3.25,-8.28 C4.98,-7.22 6.77,-5.88 6.7,-2.4 C6.64,1.08 4.48,2.26 3.2,3.05 C1.92,3.83 -1.1,3.72 -3.03,2.76 C-4.97,1.79 -6.38,0.3 -6.37,-2.59 C-6.36,-5.49 -4.65,-7.2 -2.99,-8.52c "
+                    android:valueType="pathType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.3,0 0.7,1 1.0,1.0" />
+                    </aapt:attr>
+                </objectAnimator>
+            </set>
+        </aapt:attr>
+    </target>
+    <target android:name="_R_G_L_0_G">
+        <aapt:attr name="android:animation">
+            <set android:ordering="together">
+                <objectAnimator
+                    android:propertyName="scaleX"
+                    android:duration="67"
+                    android:startOffset="0"
+                    android:valueFrom="0"
+                    android:valueTo="1"
+                    android:valueType="floatType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
+                    </aapt:attr>
+                </objectAnimator>
+                <objectAnimator
+                    android:propertyName="scaleY"
+                    android:duration="67"
+                    android:startOffset="0"
+                    android:valueFrom="0"
+                    android:valueTo="1"
+                    android:valueType="floatType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
+                    </aapt:attr>
+                </objectAnimator>
+            </set>
+        </aapt:attr>
+    </target>
+    <target android:name="time_group">
+        <aapt:attr name="android:animation">
+            <set android:ordering="together">
+                <objectAnimator
+                    android:propertyName="translateX"
+                    android:duration="500"
+                    android:startOffset="0"
+                    android:valueFrom="0"
+                    android:valueTo="1"
+                    android:valueType="floatType" />
+            </set>
+        </aapt:attr>
+    </target>
+</animated-vector>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/pin_dot_shape_6_avd.xml b/packages/SystemUI/res/drawable/pin_dot_shape_6_avd.xml
index ddda94a..06e27df 100644
--- a/packages/SystemUI/res/drawable/pin_dot_shape_6_avd.xml
+++ b/packages/SystemUI/res/drawable/pin_dot_shape_6_avd.xml
@@ -1 +1,142 @@
-<animated-vector xmlns:android="http://schemas.android.com/apk/res/android" xmlns:aapt="http://schemas.android.com/aapt"><aapt:attr name="android:drawable"><vector android:height="30dp" android:width="30dp" android:viewportHeight="30" android:viewportWidth="30"><group android:name="_R_G"><group android:name="_R_G_L_1_G" android:translateX="15.397" android:translateY="15.691" android:scaleY="0"><path android:name="_R_G_L_1_G_D_0_P_0" android:fillColor="#ffffff" android:fillAlpha="1" android:fillType="nonZero" android:pathData=" M-0.46 -13.65 C3.05,-13.65 12.63,-6.57 12.9,-3.63 C12.98,-2.7 12.65,12.85 -0.46,12.85 C-13.57,12.85 -13.77,-2.84 -13.76,-3.63 C-13.72,-6.87 -3.96,-13.65 -0.46,-13.65c "/></group><group android:name="_R_G_L_0_G" android:translateX="15" android:translateY="15.859" android:pivotY="-0.859" android:scaleX="0" android:scaleY="0"><path android:name="_R_G_L_0_G_D_0_P_0" android:fillColor="#ffffff" android:fillAlpha="1" android:fillType="nonZero" android:pathData=" M-2.82 -14.07 C-1.14,-15.29 1.14,-15.29 2.82,-14.07 C2.82,-14.07 7.73,-10.53 7.73,-10.53 C7.73,-10.53 12.58,-7 12.58,-7 C14.29,-5.76 15,-3.55 14.35,-1.54 C14.35,-1.54 12.51,4.14 12.51,4.14 C12.51,4.14 10.64,9.85 10.64,9.85 C9.99,11.84 8.14,13.19 6.05,13.19 C6.05,13.19 0,13.2 0,13.2 C0,13.2 -6.05,13.19 -6.05,13.19 C-8.14,13.19 -9.98,11.84 -10.64,9.85 C-10.64,9.85 -12.51,4.14 -12.51,4.14 C-12.51,4.14 -14.35,-1.54 -14.35,-1.54 C-15,-3.55 -14.29,-5.76 -12.58,-7 C-12.58,-7 -7.73,-10.53 -7.73,-10.53 C-7.73,-10.53 -2.82,-14.07 -2.82,-14.07c "/></group></group><group android:name="time_group"/></vector></aapt:attr><target android:name="_R_G_L_1_G_D_0_P_0"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="pathData" android:duration="67" android:startOffset="0" android:valueFrom="M-0.46 -13.65 C3.05,-13.65 12.63,-6.57 12.9,-3.63 C12.98,-2.7 12.65,12.85 -0.46,12.85 C-13.57,12.85 -13.77,-2.84 -13.76,-3.63 C-13.72,-6.87 -3.96,-13.65 -0.46,-13.65c " android:valueTo="M-0.46 -13.65 C3.05,-13.65 12.63,-6.57 12.9,-3.63 C12.98,-2.7 12.65,12.85 -0.46,12.85 C-13.57,12.85 -13.77,-2.84 -13.76,-3.63 C-13.72,-6.87 -3.96,-13.65 -0.46,-13.65c " android:valueType="pathType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.167,0 0.8,1 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="pathData" android:duration="283" android:startOffset="67" android:valueFrom="M-0.46 -13.65 C3.05,-13.65 12.63,-6.57 12.9,-3.63 C12.98,-2.7 12.65,12.85 -0.46,12.85 C-13.57,12.85 -13.77,-2.84 -13.76,-3.63 C-13.72,-6.87 -3.96,-13.65 -0.46,-13.65c " android:valueTo="M-0.44 -8.69 C3.98,-8.69 7.56,-5.11 7.56,-0.69 C7.56,3.73 3.98,7.31 -0.44,7.31 C-4.86,7.31 -8.44,3.73 -8.44,-0.69 C-8.44,-5.11 -4.86,-8.69 -0.44,-8.69c " android:valueType="pathType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.167,0 0.8,1 1.0,1.0"/></aapt:attr></objectAnimator></set></aapt:attr></target><target android:name="_R_G_L_1_G"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="scaleY" android:duration="0" android:startOffset="67" android:valueFrom="0" android:valueTo="1" android:valueType="floatType"/></set></aapt:attr></target><target android:name="_R_G_L_0_G"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="scaleX" android:duration="67" android:startOffset="0" android:valueFrom="0" android:valueTo="1" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="scaleY" android:duration="67" android:startOffset="0" android:valueFrom="0" android:valueTo="1" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="scaleX" android:duration="283" android:startOffset="67" android:valueFrom="1" android:valueTo="0.35000000000000003" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.7,0 0.6,1 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="scaleY" android:duration="283" android:startOffset="67" android:valueFrom="1" android:valueTo="0.35000000000000003" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.7,0 0.6,1 1.0,1.0"/></aapt:attr></objectAnimator></set></aapt:attr></target><target android:name="_R_G_L_0_G"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="scaleY" android:duration="0" android:startOffset="0" android:valueFrom="0" android:valueTo="1" android:valueType="floatType"/></set></aapt:attr></target><target android:name="_R_G_L_0_G"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="scaleY" android:duration="0" android:startOffset="367" android:valueFrom="1" android:valueTo="0" android:valueType="floatType"/></set></aapt:attr></target><target android:name="time_group"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="translateX" android:duration="500" android:startOffset="0" android:valueFrom="0" android:valueTo="1" android:valueType="floatType"/></set></aapt:attr></target></animated-vector>
\ No newline at end of file
+<animated-vector xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:aapt="http://schemas.android.com/aapt">
+    <aapt:attr name="android:drawable">
+        <vector
+            android:height="30dp"
+            android:width="30dp"
+            android:viewportHeight="30"
+            android:viewportWidth="30">
+            <group android:name="_R_G">
+                <group
+                    android:name="_R_G_L_1_G"
+                    android:translateX="15.397"
+                    android:translateY="15.691"
+                    android:scaleY="0">
+                    <path
+                        android:name="_R_G_L_1_G_D_0_P_0"
+                        android:fillColor="#ffffff"
+                        android:fillAlpha="1"
+                        android:fillType="nonZero"
+                        android:pathData=" M-0.46 -13.65 C3.05,-13.65 12.63,-6.57 12.9,-3.63 C12.98,-2.7 12.65,12.85 -0.46,12.85 C-13.57,12.85 -13.77,-2.84 -13.76,-3.63 C-13.72,-6.87 -3.96,-13.65 -0.46,-13.65c " />
+                </group>
+                <group
+                    android:name="_R_G_L_0_G"
+                    android:translateX="15"
+                    android:translateY="15.859"
+                    android:pivotY="-0.859">
+                    <path
+                        android:name="_R_G_L_0_G_D_0_P_0"
+                        android:fillColor="#ffffff"
+                        android:fillAlpha="1"
+                        android:fillType="nonZero"
+                        android:pathData=" M-2.82 -14.07 C-1.14,-15.29 1.14,-15.29 2.82,-14.07 C2.82,-14.07 7.73,-10.53 7.73,-10.53 C7.73,-10.53 12.58,-7 12.58,-7 C14.29,-5.76 15,-3.55 14.35,-1.54 C14.35,-1.54 12.51,4.14 12.51,4.14 C12.51,4.14 10.64,9.85 10.64,9.85 C9.99,11.84 8.14,13.19 6.05,13.19 C6.05,13.19 0,13.2 0,13.2 C0,13.2 -6.05,13.19 -6.05,13.19 C-8.14,13.19 -9.98,11.84 -10.64,9.85 C-10.64,9.85 -12.51,4.14 -12.51,4.14 C-12.51,4.14 -14.35,-1.54 -14.35,-1.54 C-15,-3.55 -14.29,-5.76 -12.58,-7 C-12.58,-7 -7.73,-10.53 -7.73,-10.53 C-7.73,-10.53 -2.82,-14.07 -2.82,-14.07c " />
+                </group>
+            </group>
+            <group android:name="time_group" />
+        </vector>
+    </aapt:attr>
+    <target android:name="_R_G_L_1_G_D_0_P_0">
+        <aapt:attr name="android:animation">
+            <set android:ordering="together">
+                <objectAnimator
+                    android:propertyName="pathData"
+                    android:duration="67"
+                    android:startOffset="0"
+                    android:valueFrom="M-0.46 -13.65 C3.05,-13.65 12.63,-6.57 12.9,-3.63 C12.98,-2.7 12.65,12.85 -0.46,12.85 C-13.57,12.85 -13.77,-2.84 -13.76,-3.63 C-13.72,-6.87 -3.96,-13.65 -0.46,-13.65c "
+                    android:valueTo="M-0.46 -13.65 C3.05,-13.65 12.63,-6.57 12.9,-3.63 C12.98,-2.7 12.65,12.85 -0.46,12.85 C-13.57,12.85 -13.77,-2.84 -13.76,-3.63 C-13.72,-6.87 -3.96,-13.65 -0.46,-13.65c "
+                    android:valueType="pathType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0 0.8,1 1.0,1.0" />
+                    </aapt:attr>
+                </objectAnimator>
+                <objectAnimator
+                    android:propertyName="pathData"
+                    android:duration="283"
+                    android:startOffset="67"
+                    android:valueFrom="M-0.46 -13.65 C3.05,-13.65 12.63,-6.57 12.9,-3.63 C12.98,-2.7 12.65,12.85 -0.46,12.85 C-13.57,12.85 -13.77,-2.84 -13.76,-3.63 C-13.72,-6.87 -3.96,-13.65 -0.46,-13.65c "
+                    android:valueTo="M-0.44 -8.69 C3.98,-8.69 7.56,-5.11 7.56,-0.69 C7.56,3.73 3.98,7.31 -0.44,7.31 C-4.86,7.31 -8.44,3.73 -8.44,-0.69 C-8.44,-5.11 -4.86,-8.69 -0.44,-8.69c "
+                    android:valueType="pathType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0 0.8,1 1.0,1.0" />
+                    </aapt:attr>
+                </objectAnimator>
+            </set>
+        </aapt:attr>
+    </target>
+    <target android:name="_R_G_L_1_G">
+        <aapt:attr name="android:animation">
+            <set android:ordering="together">
+                <objectAnimator
+                    android:propertyName="scaleY"
+                    android:duration="0"
+                    android:startOffset="67"
+                    android:valueFrom="0"
+                    android:valueTo="1"
+                    android:valueType="floatType" />
+            </set>
+        </aapt:attr>
+    </target>
+    <target android:name="_R_G_L_0_G">
+        <aapt:attr name="android:animation">
+            <set android:ordering="together">
+                <objectAnimator
+                    android:propertyName="scaleX"
+                    android:duration="67"
+                    android:startOffset="0"
+                    android:valueFrom="0"
+                    android:valueTo="1"
+                    android:valueType="floatType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
+                    </aapt:attr>
+                </objectAnimator>
+                <objectAnimator
+                    android:propertyName="scaleY"
+                    android:duration="67"
+                    android:startOffset="0"
+                    android:valueFrom="0"
+                    android:valueTo="1"
+                    android:valueType="floatType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
+                    </aapt:attr>
+                </objectAnimator>
+                <objectAnimator
+                    android:propertyName="scaleX"
+                    android:duration="283"
+                    android:startOffset="67"
+                    android:valueFrom="1"
+                    android:valueTo="0.35000000000000003"
+                    android:valueType="floatType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.7,0 0.6,1 1.0,1.0" />
+                    </aapt:attr>
+                </objectAnimator>
+                <objectAnimator
+                    android:propertyName="scaleY"
+                    android:duration="283"
+                    android:startOffset="67"
+                    android:valueFrom="1"
+                    android:valueTo="0.35000000000000003"
+                    android:valueType="floatType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.7,0 0.6,1 1.0,1.0" />
+                    </aapt:attr>
+                </objectAnimator>
+            </set>
+        </aapt:attr>
+    </target>
+    <target android:name="time_group">
+        <aapt:attr name="android:animation">
+            <set android:ordering="together">
+                <objectAnimator
+                    android:propertyName="translateX"
+                    android:duration="500"
+                    android:startOffset="0"
+                    android:valueFrom="0"
+                    android:valueTo="1"
+                    android:valueType="floatType" />
+            </set>
+        </aapt:attr>
+    </target>
+</animated-vector>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/dream_overlay_status_bar_view.xml b/packages/SystemUI/res/layout/dream_overlay_status_bar_view.xml
index 50dcaf3..bb32022 100644
--- a/packages/SystemUI/res/layout/dream_overlay_status_bar_view.xml
+++ b/packages/SystemUI/res/layout/dream_overlay_status_bar_view.xml
@@ -57,7 +57,6 @@
             android:layout_width="@dimen/dream_overlay_status_bar_icon_size"
             android:layout_height="match_parent"
             android:layout_marginStart="@dimen/dream_overlay_status_icon_margin"
-            android:layout_marginTop="@dimen/dream_overlay_status_bar_marginTop"
             android:src="@drawable/ic_alarm"
             android:tint="@android:color/white"
             android:visibility="gone"
@@ -68,7 +67,6 @@
             android:layout_width="@dimen/dream_overlay_status_bar_icon_size"
             android:layout_height="match_parent"
             android:layout_marginStart="@dimen/dream_overlay_status_icon_margin"
-            android:layout_marginTop="@dimen/dream_overlay_status_bar_marginTop"
             android:src="@drawable/ic_qs_dnd_on"
             android:tint="@android:color/white"
             android:visibility="gone"
@@ -79,7 +77,6 @@
             android:layout_width="@dimen/dream_overlay_status_bar_icon_size"
             android:layout_height="match_parent"
             android:layout_marginStart="@dimen/dream_overlay_status_icon_margin"
-            android:layout_marginTop="@dimen/dream_overlay_status_bar_marginTop"
             android:src="@drawable/ic_signal_wifi_off"
             android:visibility="gone"
             android:contentDescription="@string/dream_overlay_status_bar_wifi_off" />
diff --git a/packages/SystemUI/res/layout/media_output_list_item_advanced.xml b/packages/SystemUI/res/layout/media_output_list_item_advanced.xml
index 7105721..21e0d2c 100644
--- a/packages/SystemUI/res/layout/media_output_list_item_advanced.xml
+++ b/packages/SystemUI/res/layout/media_output_list_item_advanced.xml
@@ -164,7 +164,6 @@
             />
         <ImageView
             android:id="@+id/media_output_item_end_click_icon"
-            android:src="@drawable/media_output_status_edit_session"
             android:layout_width="24dp"
             android:layout_height="24dp"
             android:focusable="false"
diff --git a/packages/SystemUI/res/layout/screen_record_options.xml b/packages/SystemUI/res/layout/screen_record_options.xml
index 6cc72dd..d9f4b79 100644
--- a/packages/SystemUI/res/layout/screen_record_options.xml
+++ b/packages/SystemUI/res/layout/screen_record_options.xml
@@ -55,14 +55,13 @@
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
         android:orientation="horizontal"
+        android:gravity="center_vertical"
         android:layout_marginTop="@dimen/screenrecord_option_padding">
         <ImageView
             android:layout_width="@dimen/screenrecord_option_icon_size"
             android:layout_height="@dimen/screenrecord_option_icon_size"
-            android:layout_weight="0"
             android:src="@drawable/ic_touch"
             android:tint="?android:attr/textColorSecondary"
-            android:layout_gravity="center_vertical"
             android:layout_marginRight="@dimen/screenrecord_option_padding"
             android:importantForAccessibility="no"/>
         <TextView
@@ -70,7 +69,6 @@
             android:layout_height="wrap_content"
             android:minHeight="48dp"
             android:layout_weight="1"
-            android:gravity="center_vertical"
             android:text="@string/screenrecord_taps_label"
             android:textAppearance="?android:attr/textAppearanceMedium"
             android:fontFamily="@*android:string/config_bodyFontFamily"
@@ -80,7 +78,6 @@
             android:layout_width="wrap_content"
             android:minWidth="48dp"
             android:layout_height="48dp"
-            android:layout_weight="0"
             android:id="@+id/screenrecord_taps_switch"
             style="@style/ScreenRecord.Switch"
             android:importantForAccessibility="yes"/>
diff --git a/packages/SystemUI/res/layout/udfps_keyguard_view_legacy.xml b/packages/SystemUI/res/layout/udfps_keyguard_view_legacy.xml
index 00af7f4..530d752 100644
--- a/packages/SystemUI/res/layout/udfps_keyguard_view_legacy.xml
+++ b/packages/SystemUI/res/layout/udfps_keyguard_view_legacy.xml
@@ -16,8 +16,7 @@
   -->
 <com.android.systemui.biometrics.UdfpsKeyguardViewLegacy
     xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:app="http://schemas.android.com/apk/res-auto"
-    android:id="@+id/udfps_animation_view"
+    android:id="@+id/udfps_animation_view_legacy"
     android:layout_width="match_parent"
     android:layout_height="match_parent">
 
diff --git a/packages/SystemUI/res/values-sw600dp/dimens.xml b/packages/SystemUI/res/values-sw600dp/dimens.xml
index 59becc6..9a2db6bfd 100644
--- a/packages/SystemUI/res/values-sw600dp/dimens.xml
+++ b/packages/SystemUI/res/values-sw600dp/dimens.xml
@@ -20,6 +20,13 @@
          On large screens should be the same as the regular status bar. -->
     <dimen name="status_bar_header_height_keyguard">@dimen/status_bar_height</dimen>
 
+    <!-- padding for container with status icons and battery -->
+    <dimen name="status_bar_icons_padding_end">12dp</dimen>
+    <!-- start padding is smaller to account for status icon margins coming from drawable itself -->
+    <dimen name="status_bar_icons_padding_start">11dp</dimen>
+
+    <dimen name="status_bar_padding_end">0dp</dimen>
+
     <!-- Size of user icon + frame in the qs user picker (incl. frame) -->
     <dimen name="qs_framed_avatar_size">60dp</dimen>
     <!-- Size of user icon + frame in the keyguard user picker (incl. frame) -->
diff --git a/packages/SystemUI/res/values-sw720dp/dimens.xml b/packages/SystemUI/res/values-sw720dp/dimens.xml
index d277dae..d053a7a 100644
--- a/packages/SystemUI/res/values-sw720dp/dimens.xml
+++ b/packages/SystemUI/res/values-sw720dp/dimens.xml
@@ -16,13 +16,9 @@
 */
 -->
 <resources>
-    <!-- padding for container with status icons and battery -->
-    <dimen name="status_bar_icons_padding_end">12dp</dimen>
-    <!-- it's a bit smaller on large screen to account for status_bar_icon_horizontal_margin -->
+    <!-- it's a bit smaller on 720dp to account for status_bar_icon_horizontal_margin -->
     <dimen name="status_bar_icons_padding_start">10dp</dimen>
 
-    <dimen name="status_bar_padding_end">0dp</dimen>
-
     <!-- gap on either side of status bar notification icons -->
     <dimen name="status_bar_icon_horizontal_margin">1dp</dimen>
 
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 3bd7a06..d9466bf 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -1793,7 +1793,6 @@
     <dimen name="dream_overlay_status_bar_ambient_text_shadow_dy">0.5dp</dimen>
     <dimen name="dream_overlay_status_bar_ambient_text_shadow_radius">2dp</dimen>
     <dimen name="dream_overlay_icon_inset_dimen">0dp</dimen>
-    <dimen name="dream_overlay_status_bar_marginTop">22dp</dimen>
 
     <!-- Default device corner radius, used for assist UI -->
     <dimen name="config_rounded_mask_size">0px</dimen>
diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml
index fd74c7e..6b85621 100644
--- a/packages/SystemUI/res/values/styles.xml
+++ b/packages/SystemUI/res/values/styles.xml
@@ -398,7 +398,8 @@
         <item name="android:itemTextAppearance">@style/Control.MenuItem</item>
     </style>
 
-    <style name="Theme.SystemUI.QuickSettings.BrightnessDialog" parent="@android:style/Theme.DeviceDefault.Dialog">
+    <!-- Cannot double inherit. Use Theme.SystemUI.QuickSettings in code to match -->
+    <style name="BrightnessDialog" parent="@android:style/Theme.DeviceDefault.Dialog">
         <item name="android:windowBackground">@android:color/transparent</item>
     </style>
 
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java
index 3c447a8..ca064ef 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java
@@ -324,7 +324,7 @@
      * Returns whether the specified sysui state is such that the back gesture should be
      * disabled.
      */
-    public static boolean isBackGestureDisabled(int sysuiStateFlags) {
+    public static boolean isBackGestureDisabled(int sysuiStateFlags, boolean forTrackpad) {
         // Always allow when the bouncer/global actions/voice session is showing (even on top of
         // the keyguard)
         if ((sysuiStateFlags & SYSUI_STATE_BOUNCER_SHOWING) != 0
@@ -335,16 +335,23 @@
         if ((sysuiStateFlags & SYSUI_STATE_ALLOW_GESTURE_IGNORING_BAR_VISIBILITY) != 0) {
             sysuiStateFlags &= ~SYSUI_STATE_NAV_BAR_HIDDEN;
         }
+
+        return (sysuiStateFlags & getBackGestureDisabledMask(forTrackpad)) != 0;
+    }
+
+    private static int getBackGestureDisabledMask(boolean forTrackpad) {
         // Disable when in immersive, or the notifications are interactive
-        int disableFlags = SYSUI_STATE_NAV_BAR_HIDDEN | SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING;
+        int disableFlags = SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING;
+        if (!forTrackpad) {
+            disableFlags |= SYSUI_STATE_NAV_BAR_HIDDEN;
+        }
 
         // EdgeBackGestureHandler ignores Back gesture when SYSUI_STATE_NOTIFICATION_PANEL_EXPANDED.
         // To allow Shade to respond to Back, we're bypassing this check (behind a flag).
         if (!ALLOW_BACK_GESTURE_IN_SHADE) {
             disableFlags |= SYSUI_STATE_NOTIFICATION_PANEL_EXPANDED;
         }
-
-        return (sysuiStateFlags & disableFlags) != 0;
+        return disableFlags;
     }
 
     /**
diff --git a/packages/SystemUI/shared/src/com/android/systemui/util/TraceUtils.kt b/packages/SystemUI/shared/src/com/android/systemui/util/TraceUtils.kt
index 64234c2..e02e592 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/util/TraceUtils.kt
+++ b/packages/SystemUI/shared/src/com/android/systemui/util/TraceUtils.kt
@@ -18,22 +18,23 @@
 
 import android.os.Trace
 import android.os.TraceNameSupplier
+import java.util.concurrent.atomic.AtomicInteger
 
 /**
- * Run a block within a [Trace] section.
- * Calls [Trace.beginSection] before and [Trace.endSection] after the passed block.
+ * Run a block within a [Trace] section. Calls [Trace.beginSection] before and [Trace.endSection]
+ * after the passed block.
  */
 inline fun <T> traceSection(tag: String, block: () -> T): T =
-        if (Trace.isTagEnabled(Trace.TRACE_TAG_APP)) {
-            Trace.traceBegin(Trace.TRACE_TAG_APP, tag)
-            try {
-                block()
-            } finally {
-                Trace.traceEnd(Trace.TRACE_TAG_APP)
-            }
-        } else {
+    if (Trace.isTagEnabled(Trace.TRACE_TAG_APP)) {
+        Trace.traceBegin(Trace.TRACE_TAG_APP, tag)
+        try {
             block()
+        } finally {
+            Trace.traceEnd(Trace.TRACE_TAG_APP)
         }
+    } else {
+        block()
+    }
 
 class TraceUtils {
     companion object {
@@ -43,6 +44,7 @@
 
         /**
          * Helper function for creating a Runnable object that implements TraceNameSupplier.
+         *
          * This is useful for posting Runnables to Handlers with meaningful names.
          */
         inline fun namedRunnable(tag: String, crossinline block: () -> Unit): Runnable {
@@ -51,5 +53,37 @@
                 override fun run() = block()
             }
         }
+
+        /**
+         * Cookie used for async traces. Shouldn't be public, but to use it inside inline methods
+         * there is no other way around.
+         */
+        val lastCookie = AtomicInteger(0)
+
+        /**
+         * Creates an async slice in a track called "AsyncTraces".
+         *
+         * This can be used to trace coroutine code. Note that all usages of this method will appear
+         * under a single track.
+         */
+        inline fun <T> traceAsync(method: String, block: () -> T): T =
+            traceAsync(method, "AsyncTraces", block)
+
+        /**
+         * Creates an async slice in a track with [trackName] while [block] runs.
+         *
+         * This can be used to trace coroutine code. [method] will be the name of the slice,
+         * [trackName] of the track. The track is one of the rows visible in a perfetto trace inside
+         * SystemUI process.
+         */
+        inline fun <T> traceAsync(method: String, trackName: String, block: () -> T): T {
+            val cookie = lastCookie.incrementAndGet()
+            Trace.asyncTraceForTrackBegin(Trace.TRACE_TAG_APP, trackName, method, cookie)
+            try {
+                return block()
+            } finally {
+                Trace.asyncTraceForTrackEnd(Trace.TRACE_TAG_APP, trackName, cookie)
+            }
+        }
     }
 }
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
index dba1246..6c98376 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
@@ -352,6 +352,13 @@
     }
 
     /**
+     * Set if the split shade is enabled
+     */
+    public void setSplitShadeEnabled(boolean splitShadeEnabled) {
+        mSmartspaceController.setSplitShadeEnabled(splitShadeEnabled);
+    }
+
+    /**
      * Set which clock should be displayed on the keyguard. The other one will be automatically
      * hidden.
      */
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPinViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPinViewController.java
index 6f59684..3e16d55 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPinViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPinViewController.java
@@ -43,15 +43,6 @@
     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,
@@ -72,9 +63,6 @@
         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
@@ -94,7 +82,7 @@
 
     protected void onUserInput() {
         super.onUserInput();
-        if (mIsAutoPinConfirmEnabledInSettings) {
+        if (isAutoPinConfirmEnabledInSettings()) {
             updateAutoConfirmationState();
             if (mPasswordEntry.getText().length() == mPinLength
                     && mOkButton.getVisibility() == View.INVISIBLE) {
@@ -142,7 +130,7 @@
      * Updates the visibility of the OK button for auto confirm feature
      */
     private void updateOKButtonVisibility() {
-        if (mIsPinHinting && !mDisabledAutoConfirmation) {
+        if (isAutoPinConfirmEnabledInSettings() && !mDisabledAutoConfirmation) {
             mOkButton.setVisibility(View.INVISIBLE);
         } else {
             mOkButton.setVisibility(View.VISIBLE);
@@ -154,9 +142,10 @@
      * Visibility changes are only for auto confirmation configuration.
      */
     private void updateBackSpaceVisibility() {
+        boolean isAutoConfirmation = isAutoPinConfirmEnabledInSettings();
         mBackspaceKey.setTransparentMode(/* isTransparentMode= */
-                mIsAutoPinConfirmEnabledInSettings && !mDisabledAutoConfirmation);
-        if (mIsAutoPinConfirmEnabledInSettings) {
+                isAutoConfirmation && !mDisabledAutoConfirmation);
+        if (isAutoConfirmation) {
             if (mPasswordEntry.getText().length() > 0
                     || mDisabledAutoConfirmation) {
                 mBackspaceKey.setVisibility(View.VISIBLE);
@@ -166,8 +155,24 @@
         }
     }
     /** Updates whether to use pin hinting or not. */
-    private void updatePinHinting() {
-        mPasswordEntry.setIsPinHinting(mIsAutoPinConfirmEnabledInSettings && mIsPinHinting
+    void updatePinHinting() {
+        mPasswordEntry.setIsPinHinting(isAutoPinConfirmEnabledInSettings() && isPinHinting()
                 && !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/KeyguardSecurityContainerController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
index 880f242..458ca2b 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
@@ -544,7 +544,6 @@
     public void setOnDismissAction(ActivityStarter.OnDismissAction action, Runnable cancelAction) {
         if (mCancelAction != null) {
             mCancelAction.run();
-            mCancelAction = null;
         }
         mDismissAction = action;
         mCancelAction = cancelAction;
@@ -782,9 +781,10 @@
                 case SimPuk:
                     // Shortcut for SIM PIN/PUK to go to directly to user's security screen or home
                     SecurityMode securityMode = mSecurityModel.getSecurityMode(targetUserId);
-                    if (securityMode == SecurityMode.None || mLockPatternUtils.isLockScreenDisabled(
-                            KeyguardUpdateMonitor.getCurrentUser())) {
-                        finish = true;
+                    boolean isLockscreenDisabled = mLockPatternUtils.isLockScreenDisabled(
+                            KeyguardUpdateMonitor.getCurrentUser());
+                    if (securityMode == SecurityMode.None || isLockscreenDisabled) {
+                        finish = isLockscreenDisabled;
                         eventSubtype = BOUNCER_DISMISS_SIM;
                         uiEvent = BouncerUiEvent.BOUNCER_DISMISS_SIM;
                     } else {
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java
index 00500d6..6854c97 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java
@@ -323,6 +323,13 @@
     }
 
     /**
+     * Set if the split shade is enabled
+     */
+    public void setSplitShadeEnabled(boolean enabled) {
+        mKeyguardClockSwitchController.setSplitShadeEnabled(enabled);
+    }
+
+    /**
      * Updates the alignment of the KeyguardStatusView and animates the transition if requested.
      */
     public void updateAlignment(
@@ -350,6 +357,9 @@
         }
 
         mInteractionJankMonitor.begin(mView, CUJ_LOCKSCREEN_CLOCK_MOVE_ANIMATION);
+        /* This transition blocks any layout changes while running. For that reason
+        * special logic with setting visibility was added to {@link BcSmartspaceView#setDozing}
+        * for split shade to avoid jump of the media object. */
         ChangeBounds transition = new ChangeBounds();
         if (splitShadeEnabled) {
             // Excluding media from the transition on split-shade, as it doesn't transition
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
index c4ea45d..518baec 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
@@ -303,6 +303,11 @@
     private static final ComponentName FALLBACK_HOME_COMPONENT = new ComponentName(
             "com.android.settings", "com.android.settings.FallbackHome");
 
+    private static final List<Integer> ABSENT_SIM_STATE_LIST = Arrays.asList(
+            TelephonyManager.SIM_STATE_ABSENT,
+            TelephonyManager.SIM_STATE_UNKNOWN,
+            TelephonyManager.SIM_STATE_NOT_READY);
+
     private final Context mContext;
     private final UserTracker mUserTracker;
     private final KeyguardUpdateMonitorLogger mLogger;
@@ -3742,8 +3747,7 @@
         mLogger.logSimState(subId, slotId, state);
 
         boolean becameAbsent = false;
-        if (!SubscriptionManager.isValidSubscriptionId(subId)
-                && state != TelephonyManager.SIM_STATE_UNKNOWN) {
+        if (!SubscriptionManager.isValidSubscriptionId(subId)) {
             mLogger.w("invalid subId in handleSimStateChange()");
             /* Only handle No SIM(ABSENT) and Card Error(CARD_IO_ERROR) due to
              * handleServiceStateChange() handle other case */
@@ -3761,11 +3765,11 @@
                 }
             } else if (state == TelephonyManager.SIM_STATE_CARD_IO_ERROR) {
                 updateTelephonyCapable(true);
-            } else {
-                return;
             }
         }
 
+        becameAbsent |= ABSENT_SIM_STATE_LIST.contains(state);
+
         SimData data = mSimDatas.get(subId);
         final boolean changed;
         if (data == null) {
@@ -3778,7 +3782,7 @@
             data.subId = subId;
             data.slotId = slotId;
         }
-        if ((changed || becameAbsent) || state == TelephonyManager.SIM_STATE_UNKNOWN) {
+        if ((changed || becameAbsent)) {
             for (int i = 0; i < mCallbacks.size(); i++) {
                 KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
                 if (cb != null) {
diff --git a/packages/SystemUI/src/com/android/keyguard/LockIconView.java b/packages/SystemUI/src/com/android/keyguard/LockIconView.java
index d1fffaa..76b073e 100644
--- a/packages/SystemUI/src/com/android/keyguard/LockIconView.java
+++ b/packages/SystemUI/src/com/android/keyguard/LockIconView.java
@@ -32,7 +32,6 @@
 
 import androidx.annotation.IntDef;
 import androidx.annotation.NonNull;
-import androidx.annotation.VisibleForTesting;
 
 import com.android.internal.graphics.ColorUtils;
 import com.android.settingslib.Utils;
@@ -126,7 +125,6 @@
     /**
      * Set the location of the lock icon.
      */
-    @VisibleForTesting
     public void setCenterLocation(@NonNull Point center, float radius, int drawablePadding) {
         mLockIconCenter = center;
         mRadius = radius;
diff --git a/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java b/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java
index 239a0cc..8ef6c2e 100644
--- a/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java
@@ -56,10 +56,12 @@
 import com.android.systemui.biometrics.AuthController;
 import com.android.systemui.biometrics.AuthRippleController;
 import com.android.systemui.biometrics.UdfpsController;
+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.dump.DumpManager;
 import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.flags.Flags;
 import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor;
 import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor;
 import com.android.systemui.keyguard.shared.model.TransitionStep;
@@ -112,6 +114,7 @@
     @NonNull private final VibratorHelper mVibrator;
     @Nullable private final AuthRippleController mAuthRippleController;
     @NonNull private final FeatureFlags mFeatureFlags;
+    @NonNull private final PrimaryBouncerInteractor mPrimaryBouncerInteractor;
     @NonNull private final KeyguardTransitionInteractor mTransitionInteractor;
     @NonNull private final KeyguardInteractor mKeyguardInteractor;
 
@@ -180,7 +183,8 @@
             @NonNull @Main Resources resources,
             @NonNull KeyguardTransitionInteractor transitionInteractor,
             @NonNull KeyguardInteractor keyguardInteractor,
-            @NonNull FeatureFlags featureFlags
+            @NonNull FeatureFlags featureFlags,
+            PrimaryBouncerInteractor primaryBouncerInteractor
     ) {
         super(view);
         mStatusBarStateController = statusBarStateController;
@@ -197,6 +201,7 @@
         mTransitionInteractor = transitionInteractor;
         mKeyguardInteractor = keyguardInteractor;
         mFeatureFlags = featureFlags;
+        mPrimaryBouncerInteractor = primaryBouncerInteractor;
 
         mMaxBurnInOffsetX = resources.getDimensionPixelSize(R.dimen.udfps_burn_in_offset_x);
         mMaxBurnInOffsetY = resources.getDimensionPixelSize(R.dimen.udfps_burn_in_offset_y);
@@ -325,8 +330,14 @@
             mView.setContentDescription(null);
         }
 
+        boolean accessibilityEnabled =
+                !mPrimaryBouncerInteractor.isAnimatingAway() && mView.isVisibleToUser();
+        mView.setImportantForAccessibility(
+                accessibilityEnabled ? View.IMPORTANT_FOR_ACCESSIBILITY_YES
+                        : View.IMPORTANT_FOR_ACCESSIBILITY_NO);
+
         if (!Objects.equals(prevContentDescription, mView.getContentDescription())
-                && mView.getContentDescription() != null && mView.isVisibleToUser()) {
+                && mView.getContentDescription() != null && accessibilityEnabled) {
             mView.announceForAccessibility(mView.getContentDescription());
         }
     }
@@ -387,15 +398,17 @@
     private void updateLockIconLocation() {
         final float scaleFactor = mAuthController.getScaleFactor();
         final int scaledPadding = (int) (mDefaultPaddingPx * scaleFactor);
-        if (mUdfpsSupported) {
-            mView.setCenterLocation(mAuthController.getUdfpsLocation(),
-                    mAuthController.getUdfpsRadius(), scaledPadding);
-        } else {
-            mView.setCenterLocation(
-                    new Point((int) mWidthPixels / 2,
-                            (int) (mHeightPixels
-                                    - ((mBottomPaddingPx + sLockIconRadiusPx) * scaleFactor))),
+        if (!mFeatureFlags.isEnabled(Flags.MIGRATE_LOCK_ICON)) {
+            if (mUdfpsSupported) {
+                mView.setCenterLocation(mAuthController.getUdfpsLocation(),
+                        mAuthController.getUdfpsRadius(), scaledPadding);
+            } else {
+                mView.setCenterLocation(
+                        new Point((int) mWidthPixels / 2,
+                                (int) (mHeightPixels
+                                        - ((mBottomPaddingPx + sLockIconRadiusPx) * scaleFactor))),
                         sLockIconRadiusPx * scaleFactor, scaledPadding);
+            }
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/ScreenDecorHwcLayer.kt b/packages/SystemUI/src/com/android/systemui/ScreenDecorHwcLayer.kt
index 670c1fa..c465585 100644
--- a/packages/SystemUI/src/com/android/systemui/ScreenDecorHwcLayer.kt
+++ b/packages/SystemUI/src/com/android/systemui/ScreenDecorHwcLayer.kt
@@ -56,9 +56,14 @@
 ) : DisplayCutoutBaseView(context) {
     val colorMode: Int
     private val useInvertedAlphaColor: Boolean
-    private val color: Int
+    private var color: Int = Color.BLACK
+        set(value) {
+            field = value
+            paint.color = value
+        }
+
     private val bgColor: Int
-    private val cornerFilter: ColorFilter
+    private var cornerFilter: ColorFilter
     private val cornerBgFilter: ColorFilter
     private val clearPaint: Paint
     @JvmField val transparentRect: Rect = Rect()
@@ -109,10 +114,16 @@
     override fun onAttachedToWindow() {
         super.onAttachedToWindow()
         parent.requestTransparentRegion(this)
+        updateColors()
+    }
+
+    private fun updateColors() {
         if (!debug) {
             viewRootImpl.setDisplayDecoration(true)
         }
 
+        cornerFilter = PorterDuffColorFilter(color, PorterDuff.Mode.SRC_IN)
+
         if (useInvertedAlphaColor) {
             paint.set(clearPaint)
         } else {
@@ -121,6 +132,21 @@
         }
     }
 
+    fun setDebugColor(color: Int) {
+        if (!debug) {
+            return
+        }
+
+        if (this.color == color) {
+            return
+        }
+
+        this.color = color
+
+        updateColors()
+        invalidate()
+    }
+
     override fun onUpdate() {
         parent.requestTransparentRegion(this)
     }
@@ -367,7 +393,7 @@
     /**
      * Update the rounded corner drawables.
      */
-    fun updateRoundedCornerDrawable(top: Drawable, bottom: Drawable) {
+    fun updateRoundedCornerDrawable(top: Drawable?, bottom: Drawable?) {
         roundedCornerDrawableTop = top
         roundedCornerDrawableBottom = bottom
         updateRoundedCornerDrawableBounds()
diff --git a/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java b/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java
index 67d4a2e..ff395da 100644
--- a/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java
+++ b/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java
@@ -36,6 +36,7 @@
 import android.graphics.Paint;
 import android.graphics.Path;
 import android.graphics.PixelFormat;
+import android.graphics.Point;
 import android.graphics.Rect;
 import android.graphics.drawable.Drawable;
 import android.hardware.graphics.common.AlphaInterpretation;
@@ -69,9 +70,9 @@
 import com.android.settingslib.Utils;
 import com.android.systemui.biometrics.AuthController;
 import com.android.systemui.dagger.SysUISingleton;
-import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.decor.CutoutDecorProviderFactory;
 import com.android.systemui.decor.DebugRoundedCornerDelegate;
+import com.android.systemui.decor.DebugRoundedCornerModel;
 import com.android.systemui.decor.DecorProvider;
 import com.android.systemui.decor.DecorProviderFactory;
 import com.android.systemui.decor.DecorProviderKt;
@@ -80,10 +81,12 @@
 import com.android.systemui.decor.PrivacyDotDecorProviderFactory;
 import com.android.systemui.decor.RoundedCornerDecorProviderFactory;
 import com.android.systemui.decor.RoundedCornerResDelegateImpl;
+import com.android.systemui.decor.ScreenDecorCommand;
 import com.android.systemui.log.ScreenDecorationsLogger;
 import com.android.systemui.qs.SettingObserver;
 import com.android.systemui.settings.DisplayTracker;
 import com.android.systemui.settings.UserTracker;
+import com.android.systemui.statusbar.commandline.CommandRegistry;
 import com.android.systemui.statusbar.events.PrivacyDotViewController;
 import com.android.systemui.util.concurrency.DelayableExecutor;
 import com.android.systemui.util.concurrency.ThreadFactory;
@@ -95,7 +98,6 @@
 import java.util.ArrayList;
 import java.util.List;
 import java.util.Objects;
-import java.util.concurrent.Executor;
 
 import javax.inject.Inject;
 
@@ -130,7 +132,7 @@
     @VisibleForTesting
     protected boolean mIsRegistered;
     private final Context mContext;
-    private final Executor mMainExecutor;
+    private final CommandRegistry mCommandRegistry;
     private final SecureSettings mSecureSettings;
     @VisibleForTesting
     DisplayTracker.Callback mDisplayListener;
@@ -170,7 +172,7 @@
     private int mTintColor = Color.BLACK;
     @VisibleForTesting
     protected DisplayDecorationSupport mHwcScreenDecorationSupport;
-    private Display.Mode mDisplayMode;
+    private final Point mDisplaySize = new Point();
     @VisibleForTesting
     protected DisplayInfo mDisplayInfo = new DisplayInfo();
     private DisplayCutout mDisplayCutout;
@@ -313,8 +315,8 @@
 
     @Inject
     public ScreenDecorations(Context context,
-            @Main Executor mainExecutor,
             SecureSettings secureSettings,
+            CommandRegistry commandRegistry,
             UserTracker userTracker,
             DisplayTracker displayTracker,
             PrivacyDotViewController dotViewController,
@@ -324,8 +326,8 @@
             ScreenDecorationsLogger logger,
             AuthController authController) {
         mContext = context;
-        mMainExecutor = mainExecutor;
         mSecureSettings = secureSettings;
+        mCommandRegistry = commandRegistry;
         mUserTracker = userTracker;
         mDisplayTracker = displayTracker;
         mDotViewController = dotViewController;
@@ -350,6 +352,45 @@
         }
     };
 
+    private final ScreenDecorCommand.Callback mScreenDecorCommandCallback = (cmd, pw) -> {
+        // If we are exiting debug mode, we can set it (false) and bail, otherwise we will
+        // ensure that debug mode is set
+        if (cmd.getDebug() != null && !cmd.getDebug()) {
+            setDebug(false);
+            return;
+        } else {
+            // setDebug is idempotent
+            setDebug(true);
+        }
+
+        if (cmd.getColor() != null) {
+            mDebugColor = cmd.getColor();
+            mExecutor.execute(() -> {
+                if (mScreenDecorHwcLayer != null) {
+                    mScreenDecorHwcLayer.setDebugColor(cmd.getColor());
+                }
+                updateColorInversionDefault();
+            });
+        }
+
+        DebugRoundedCornerModel roundedTop = null;
+        DebugRoundedCornerModel roundedBottom = null;
+        if (cmd.getRoundedTop() != null) {
+            roundedTop = cmd.getRoundedTop().toRoundedCornerDebugModel();
+        }
+        if (cmd.getRoundedBottom() != null) {
+            roundedBottom = cmd.getRoundedBottom().toRoundedCornerDebugModel();
+        }
+        if (roundedTop != null || roundedBottom != null) {
+            mDebugRoundedCornerDelegate.applyNewDebugCorners(roundedTop, roundedBottom);
+            mExecutor.execute(() -> {
+                removeAllOverlays();
+                removeHwcOverlay();
+                setupDecorations();
+            });
+        }
+    };
+
     @Override
     public void start() {
         if (DEBUG_DISABLE_SCREEN_DECORATIONS) {
@@ -361,6 +402,8 @@
         mExecutor.execute(this::startOnScreenDecorationsThread);
         mDotViewController.setUiExecutor(mExecutor);
         mAuthController.addCallback(mAuthControllerCallback);
+        mCommandRegistry.registerCommand(ScreenDecorCommand.SCREEN_DECOR_CMD_NAME,
+                () -> new ScreenDecorCommand(mScreenDecorCommandCallback));
     }
 
     /**
@@ -442,7 +485,8 @@
         mWindowManager = mContext.getSystemService(WindowManager.class);
         mContext.getDisplay().getDisplayInfo(mDisplayInfo);
         mRotation = mDisplayInfo.rotation;
-        mDisplayMode = mDisplayInfo.getMode();
+        mDisplaySize.x = mDisplayInfo.getNaturalWidth();
+        mDisplaySize.y = mDisplayInfo.getNaturalHeight();
         mDisplayUniqueId = mDisplayInfo.uniqueId;
         mDisplayCutout = mDisplayInfo.displayCutout;
         mRoundedCornerResDelegate =
@@ -463,10 +507,12 @@
             public void onDisplayChanged(int displayId) {
                 mContext.getDisplay().getDisplayInfo(mDisplayInfo);
                 final int newRotation = mDisplayInfo.rotation;
-                final Display.Mode newDisplayMode = mDisplayInfo.getMode();
                 if ((mOverlays != null || mScreenDecorHwcWindow != null)
                         && (mRotation != newRotation
-                        || displayModeChanged(mDisplayMode, newDisplayMode))) {
+                        || displaySizeChanged(mDisplaySize, mDisplayInfo))) {
+                    final Point newSize = new Point();
+                    newSize.x = mDisplayInfo.getNaturalWidth();
+                    newSize.y = mDisplayInfo.getNaturalHeight();
                     // We cannot immediately update the orientation. Otherwise
                     // WindowManager is still deferring layout until it has finished dispatching
                     // the config changes, which may cause divergence between what we draw
@@ -478,9 +524,8 @@
                     if (mRotation != newRotation) {
                         mLogger.logRotationChangeDeferred(mRotation, newRotation);
                     }
-                    if (displayModeChanged(mDisplayMode, newDisplayMode)) {
-                        mLogger.logDisplayModeChanged(
-                                newDisplayMode.getModeId(), mDisplayMode.getModeId());
+                    if (!mDisplaySize.equals(newSize)) {
+                        mLogger.logDisplaySizeChanged(mDisplaySize, newSize);
                     }
 
                     if (mOverlays != null) {
@@ -489,7 +534,7 @@
                                 final ViewGroup overlayView = mOverlays[i].getRootView();
                                 overlayView.getViewTreeObserver().addOnPreDrawListener(
                                         new RestartingPreDrawListener(
-                                                overlayView, i, newRotation, newDisplayMode));
+                                                overlayView, i, newRotation, newSize));
                             }
                         }
                     }
@@ -499,7 +544,7 @@
                                 new RestartingPreDrawListener(
                                         mScreenDecorHwcWindow,
                                         -1, // Pass -1 for views with no specific position.
-                                        newRotation, newDisplayMode));
+                                        newRotation, newSize));
                     }
                     if (mScreenDecorHwcLayer != null) {
                         mScreenDecorHwcLayer.pendingConfigChange = true;
@@ -901,15 +946,8 @@
         }
     }
 
-    private static boolean displayModeChanged(Display.Mode oldMode, Display.Mode newMode) {
-        if (oldMode == null) {
-            return true;
-        }
-
-        // We purposely ignore refresh rate and id changes here, because we don't need to
-        // invalidate for those, and they can trigger the refresh rate to increase
-        return oldMode.getPhysicalWidth() != newMode.getPhysicalWidth()
-                || oldMode.getPhysicalHeight() != newMode.getPhysicalHeight();
+    private static boolean displaySizeChanged(Point size, DisplayInfo info) {
+        return size.x != info.getNaturalWidth() || size.y != info.getNaturalHeight();
     }
 
     private int getOverlayWindowGravity(@BoundsPosition int pos) {
@@ -1128,14 +1166,14 @@
         if (mRotation != newRotation) {
             mDotViewController.setNewRotation(newRotation);
         }
-        final Display.Mode newMod = mDisplayInfo.getMode();
         final DisplayCutout newCutout = mDisplayInfo.displayCutout;
 
         if (!mPendingConfigChange
-                && (newRotation != mRotation || displayModeChanged(mDisplayMode, newMod)
+                && (newRotation != mRotation || displaySizeChanged(mDisplaySize, mDisplayInfo)
                 || !Objects.equals(newCutout, mDisplayCutout))) {
             mRotation = newRotation;
-            mDisplayMode = newMod;
+            mDisplaySize.x = mDisplayInfo.getNaturalWidth();
+            mDisplaySize.y = mDisplayInfo.getNaturalHeight();
             mDisplayCutout = newCutout;
             float ratio = getPhysicalPixelDisplaySizeRatio();
             mRoundedCornerResDelegate.setPhysicalPixelDisplaySizeRatio(ratio);
@@ -1228,7 +1266,7 @@
             bottomDrawable = mDebugRoundedCornerDelegate.getBottomRoundedDrawable();
         }
 
-        if (topDrawable == null || bottomDrawable == null) {
+        if (topDrawable == null && bottomDrawable == null) {
             return;
         }
         mScreenDecorHwcLayer.updateRoundedCornerDrawable(topDrawable, bottomDrawable);
@@ -1452,31 +1490,29 @@
 
         private final View mView;
         private final int mTargetRotation;
-        private final Display.Mode mTargetDisplayMode;
+        private final Point mTargetDisplaySize;
         // Pass -1 for ScreenDecorHwcLayer since it's a fullscreen window and has no specific
         // position.
         private final int mPosition;
 
         private RestartingPreDrawListener(View view, @BoundsPosition int position,
-                int targetRotation, Display.Mode targetDisplayMode) {
+                int targetRotation, Point targetDisplaySize) {
             mView = view;
             mTargetRotation = targetRotation;
-            mTargetDisplayMode = targetDisplayMode;
+            mTargetDisplaySize = targetDisplaySize;
             mPosition = position;
         }
 
         @Override
         public boolean onPreDraw() {
             mView.getViewTreeObserver().removeOnPreDrawListener(this);
-            if (mTargetRotation == mRotation
-                    && !displayModeChanged(mDisplayMode, mTargetDisplayMode)) {
+            if (mTargetRotation == mRotation && mDisplaySize.equals(mTargetDisplaySize)) {
                 if (DEBUG_LOGGING) {
                     final String title = mPosition < 0 ? "ScreenDecorHwcLayer"
                             : getWindowTitleByPos(mPosition);
                     Log.i(TAG, title + " already in target rot "
                             + mTargetRotation + " and in target resolution "
-                            + mTargetDisplayMode.getPhysicalWidth() + "x"
-                            + mTargetDisplayMode.getPhysicalHeight()
+                            + mTargetDisplaySize.x + "x" + mTargetDisplaySize.y
                             + ", allow draw without restarting it");
                 }
                 return true;
@@ -1491,8 +1527,7 @@
                         : getWindowTitleByPos(mPosition);
                 Log.i(TAG, title
                         + " restarting listener fired, restarting draw for rot " + mRotation
-                        + ", resolution " + mDisplayMode.getPhysicalWidth() + "x"
-                        + mDisplayMode.getPhysicalHeight());
+                        + ", resolution " + mDisplaySize.x + "x" + mDisplaySize.y);
             }
             mView.invalidate();
             return false;
@@ -1518,19 +1553,18 @@
         public boolean onPreDraw() {
             mContext.getDisplay().getDisplayInfo(mDisplayInfo);
             final int displayRotation = mDisplayInfo.rotation;
-            final Display.Mode displayMode = mDisplayInfo.getMode();
-            if ((displayRotation != mRotation || displayModeChanged(mDisplayMode, displayMode))
+            if ((displayRotation != mRotation || displaySizeChanged(mDisplaySize, mDisplayInfo))
                     && !mPendingConfigChange) {
                 if (DEBUG_LOGGING) {
                     if (displayRotation != mRotation) {
                         Log.i(TAG, "Drawing rot " + mRotation + ", but display is at rot "
                                 + displayRotation + ". Restarting draw");
                     }
-                    if (displayModeChanged(mDisplayMode, displayMode)) {
-                        Log.i(TAG, "Drawing at " + mDisplayMode.getPhysicalWidth()
-                                + "x" + mDisplayMode.getPhysicalHeight() + ", but display is at "
-                                + displayMode.getPhysicalWidth() + "x"
-                                + displayMode.getPhysicalHeight() + ". Restarting draw");
+                    if (displaySizeChanged(mDisplaySize, mDisplayInfo)) {
+                        Log.i(TAG, "Drawing at " + mDisplaySize.x + "x" + mDisplaySize.y
+                                + ", but display is at "
+                                + mDisplayInfo.getNaturalWidth() + "x"
+                                + mDisplayInfo.getNaturalHeight() + ". Restarting draw");
                     }
                 }
                 mView.invalidate();
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnification.java b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnification.java
index 2a14dc8..1739ba4 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnification.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnification.java
@@ -28,6 +28,7 @@
 import android.graphics.Rect;
 import android.hardware.display.DisplayManager;
 import android.os.Handler;
+import android.util.SparseArray;
 import android.view.Display;
 import android.view.SurfaceControl;
 import android.view.WindowManagerGlobal;
@@ -72,6 +73,9 @@
     private WindowMagnificationConnectionImpl mWindowMagnificationConnectionImpl;
     private SysUiState mSysUiState;
 
+    @VisibleForTesting
+    SparseArray<SparseArray<Float>> mUsersScales = new SparseArray();
+
     private static class ControllerSupplier extends
             DisplayIdIndexSupplier<WindowMagnificationController> {
 
@@ -295,6 +299,19 @@
         mModeSwitchesController.removeButton(displayId);
     }
 
+    @MainThread
+    void setUserMagnificationScale(int userId, int displayId, float scale) {
+        SparseArray<Float> scales = mUsersScales.get(userId);
+        if (scales == null) {
+            scales = new SparseArray<>();
+            mUsersScales.put(userId, scales);
+        }
+        if (scales.contains(displayId) && scales.get(displayId) == scale) {
+            return;
+        }
+        scales.put(displayId, scale);
+    }
+
     @VisibleForTesting
     final WindowMagnifierCallback mWindowMagnifierCallback = new WindowMagnifierCallback() {
         @Override
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationConnectionImpl.java b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationConnectionImpl.java
index c081893..f1d00ce2 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationConnectionImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationConnectionImpl.java
@@ -99,6 +99,12 @@
     }
 
     @Override
+    public void onUserMagnificationScaleChanged(int userId, int displayId, float scale) {
+        mHandler.post(() -> mWindowMagnification.setUserMagnificationScale(
+                userId, displayId, scale));
+    }
+
+    @Override
     public void setConnectionCallback(IWindowMagnificationConnectionCallback callback) {
         mConnectionCallback = callback;
     }
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationSettings.java b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationSettings.java
index 31b0f056..1e1d4b7 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationSettings.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationSettings.java
@@ -172,7 +172,8 @@
         };
     }
 
-    private class ZoomSeekbarChangeListener implements SeekBar.OnSeekBarChangeListener {
+    private class ZoomSeekbarChangeListener implements
+            SeekBarWithIconButtonsView.OnSeekBarWithIconButtonsChangeListener {
         @Override
         public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
             float scale = (progress / (float) mSeekBarMagnitude) + SCALE_MIN_VALUE;
@@ -197,6 +198,11 @@
         public void onStopTrackingTouch(SeekBar seekBar) {
             // Do nothing
         }
+
+        @Override
+        public void onUserInteractionFinalized(SeekBar seekBar, @ControlUnitType int control) {
+            // Do nothing
+        }
     }
 
     private final AccessibilityDelegate mPanelDelegate = new AccessibilityDelegate() {
@@ -521,7 +527,7 @@
                 Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_SCALE, 0,
                 UserHandle.USER_CURRENT);
         setScaleSeekbar(scale);
-        mZoomSeekbar.setOnSeekBarChangeListener(new ZoomSeekbarChangeListener());
+        mZoomSeekbar.setOnSeekBarWithIconButtonsChangeListener(new ZoomSeekbarChangeListener());
 
         mAllowDiagonalScrollingView =
                 (LinearLayout) mSettingView.findViewById(R.id.magnifier_horizontal_lock_view);
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/fontscaling/FontScalingDialog.kt b/packages/SystemUI/src/com/android/systemui/accessibility/fontscaling/FontScalingDialog.kt
index a910ab5..783460c 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/fontscaling/FontScalingDialog.kt
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/fontscaling/FontScalingDialog.kt
@@ -26,12 +26,13 @@
 import android.view.LayoutInflater
 import android.widget.Button
 import android.widget.SeekBar
-import android.widget.SeekBar.OnSeekBarChangeListener
 import android.widget.TextView
 import androidx.annotation.MainThread
 import androidx.annotation.WorkerThread
 import com.android.systemui.R
 import com.android.systemui.common.ui.view.SeekBarWithIconButtonsView
+import com.android.systemui.common.ui.view.SeekBarWithIconButtonsView.OnSeekBarWithIconButtonsChangeListener
+import com.android.systemui.common.ui.view.SeekBarWithIconButtonsView.OnSeekBarWithIconButtonsChangeListener.ControlUnitType
 import com.android.systemui.dagger.qualifiers.Background
 import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.settings.UserTracker
@@ -105,28 +106,32 @@
         lastProgress.set(fontSizeValueToIndex(currentScale))
         seekBarWithIconButtonsView.setProgress(lastProgress.get())
 
-        seekBarWithIconButtonsView.setOnSeekBarChangeListener(
-            object : OnSeekBarChangeListener {
-                var isTrackingTouch = false
-
+        seekBarWithIconButtonsView.setOnSeekBarWithIconButtonsChangeListener(
+            object : OnSeekBarWithIconButtonsChangeListener {
                 override fun onProgressChanged(seekBar: SeekBar, progress: Int, fromUser: Boolean) {
                     // Always provide preview configuration for text first when there is a change
                     // in the seekbar progress.
                     createTextPreview(progress)
-
-                    if (!isTrackingTouch) {
-                        // The seekbar progress is changed by icon buttons
-                        changeFontSize(progress, CHANGE_BY_BUTTON_DELAY_MS)
-                    }
                 }
 
                 override fun onStartTrackingTouch(seekBar: SeekBar) {
-                    isTrackingTouch = true
+                    // Do nothing
                 }
 
                 override fun onStopTrackingTouch(seekBar: SeekBar) {
-                    isTrackingTouch = false
-                    changeFontSize(seekBar.progress, CHANGE_BY_SEEKBAR_DELAY_MS)
+                    // Do nothing
+                }
+
+                override fun onUserInteractionFinalized(
+                    seekBar: SeekBar,
+                    @ControlUnitType control: Int
+                ) {
+                    if (control == ControlUnitType.BUTTON) {
+                        // The seekbar progress is changed by icon buttons
+                        changeFontSize(seekBar.progress, CHANGE_BY_BUTTON_DELAY_MS)
+                    } else {
+                        changeFontSize(seekBar.progress, CHANGE_BY_SEEKBAR_DELAY_MS)
+                    }
                 }
             }
         )
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsBpViewController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsBpViewController.kt
index e6aeb43..802eea3 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsBpViewController.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsBpViewController.kt
@@ -37,4 +37,8 @@
     dumpManager
 ) {
     override val tag = "UdfpsBpViewController"
+
+    override fun shouldPauseAuth(): Boolean {
+        return false
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
index dc9ba87..ebff0b0 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
@@ -85,6 +85,8 @@
 import com.android.systemui.flags.Flags;
 import com.android.systemui.keyguard.ScreenLifecycle;
 import com.android.systemui.keyguard.domain.interactor.KeyguardFaceAuthInteractor;
+import com.android.systemui.keyguard.ui.adapter.UdfpsKeyguardViewControllerAdapter;
+import com.android.systemui.keyguard.ui.viewmodel.UdfpsKeyguardViewModels;
 import com.android.systemui.log.SessionTracker;
 import com.android.systemui.plugins.FalsingManager;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
@@ -153,6 +155,7 @@
     @NonNull private final SystemUIDialogManager mDialogManager;
     @NonNull private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
     @NonNull private final KeyguardFaceAuthInteractor mKeyguardFaceAuthInteractor;
+    @NonNull private final Provider<UdfpsKeyguardViewModels> mUdfpsKeyguardViewModels;
     @NonNull private final VibratorHelper mVibrator;
     @NonNull private final FeatureFlags mFeatureFlags;
     @NonNull private final FalsingManager mFalsingManager;
@@ -272,7 +275,8 @@
                             (view, event, fromUdfpsView) -> onTouch(requestId, event,
                                     fromUdfpsView), mActivityLaunchAnimator, mFeatureFlags,
                             mPrimaryBouncerInteractor, mAlternateBouncerInteractor, mUdfpsUtils,
-                            mUdfpsKeyguardAccessibilityDelegate)));
+                            mUdfpsKeyguardAccessibilityDelegate,
+                            mUdfpsKeyguardViewModels)));
         }
 
         @Override
@@ -591,6 +595,13 @@
 
                 // Pilfer if valid overlap, don't allow following events to reach keyguard
                 shouldPilfer = true;
+
+                // Touch is a valid UDFPS touch. Inform the falsing manager so that the touch
+                // isn't counted against the falsing algorithm as an accidental touch.
+                // We do this on the DOWN event instead of CANCEL/UP because the CANCEL/UP events
+                // get sent too late to this receiver (after the actual cancel/up motions occur),
+                // and therefore wouldn't end up being used as part of the falsing algo.
+                mFalsingManager.isFalseTouch(UDFPS_AUTHENTICATION);
                 break;
 
             case UP:
@@ -610,7 +621,6 @@
                         data.getTime(),
                         data.getGestureStart(),
                         mStatusBarStateController.isDozing());
-                mFalsingManager.isFalseTouch(UDFPS_AUTHENTICATION);
                 break;
 
             case UNCHANGED:
@@ -784,7 +794,7 @@
     private boolean shouldTryToDismissKeyguard() {
         return mOverlay != null
                 && mOverlay.getAnimationViewController()
-                instanceof UdfpsKeyguardViewControllerLegacy
+                instanceof UdfpsKeyguardViewControllerAdapter
                 && mKeyguardStateController.canDismissLockScreen()
                 && !mAttemptedToDismissKeyguard;
     }
@@ -829,7 +839,8 @@
             @NonNull InputManager inputManager,
             @NonNull UdfpsUtils udfpsUtils,
             @NonNull KeyguardFaceAuthInteractor keyguardFaceAuthInteractor,
-            @NonNull UdfpsKeyguardAccessibilityDelegate udfpsKeyguardAccessibilityDelegate) {
+            @NonNull UdfpsKeyguardAccessibilityDelegate udfpsKeyguardAccessibilityDelegate,
+            @NonNull Provider<UdfpsKeyguardViewModels> udfpsKeyguardViewModelsProvider) {
         mContext = context;
         mExecution = execution;
         mVibrator = vibrator;
@@ -895,6 +906,7 @@
                     return Unit.INSTANCE;
                 });
         mKeyguardFaceAuthInteractor = keyguardFaceAuthInteractor;
+        mUdfpsKeyguardViewModels = udfpsKeyguardViewModelsProvider;
 
         final UdfpsOverlayController mUdfpsOverlayController = new UdfpsOverlayController();
         mFingerprintManager.setUdfpsOverlayController(mUdfpsOverlayController);
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt
index e542147..d6ef94d 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt
@@ -46,15 +46,19 @@
 import androidx.annotation.LayoutRes
 import androidx.annotation.VisibleForTesting
 import com.android.keyguard.KeyguardUpdateMonitor
-import com.android.settingslib.udfps.UdfpsUtils
 import com.android.settingslib.udfps.UdfpsOverlayParams
+import com.android.settingslib.udfps.UdfpsUtils
 import com.android.systemui.R
 import com.android.systemui.animation.ActivityLaunchAnimator
+import com.android.systemui.biometrics.ui.controller.UdfpsKeyguardViewController
+import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor
+import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor
 import com.android.systemui.dump.DumpManager
 import com.android.systemui.flags.FeatureFlags
 import com.android.systemui.flags.Flags
-import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor
-import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor
+import com.android.systemui.flags.Flags.REFACTOR_UDFPS_KEYGUARD_VIEWS
+import com.android.systemui.keyguard.ui.adapter.UdfpsKeyguardViewControllerAdapter
+import com.android.systemui.keyguard.ui.viewmodel.UdfpsKeyguardViewModels
 import com.android.systemui.plugins.statusbar.StatusBarStateController
 import com.android.systemui.shade.ShadeExpansionStateManager
 import com.android.systemui.statusbar.LockscreenShadeTransitionController
@@ -64,6 +68,8 @@
 import com.android.systemui.statusbar.policy.ConfigurationController
 import com.android.systemui.statusbar.policy.KeyguardStateController
 import com.android.systemui.util.settings.SecureSettings
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import javax.inject.Provider
 
 private const val TAG = "UdfpsControllerOverlay"
 
@@ -75,6 +81,7 @@
  * request. This state can persist across configuration changes via the [show] and [hide]
  * methods.
  */
+@ExperimentalCoroutinesApi
 @UiThread
 class UdfpsControllerOverlay @JvmOverloads constructor(
         private val context: Context,
@@ -105,6 +112,7 @@
         private val isDebuggable: Boolean = Build.IS_DEBUGGABLE,
         private val udfpsUtils: UdfpsUtils,
         private val udfpsKeyguardAccessibilityDelegate: UdfpsKeyguardAccessibilityDelegate,
+        private val udfpsKeyguardViewModels: Provider<UdfpsKeyguardViewModels>,
 ) {
     /** The view, when [isShowing], or null. */
     var overlayView: UdfpsView? = null
@@ -243,27 +251,40 @@
                 )
             }
             REASON_AUTH_KEYGUARD -> {
-                UdfpsKeyguardViewControllerLegacy(
-                    view.addUdfpsView(R.layout.udfps_keyguard_view_legacy) {
-                        updateSensorLocation(sensorBounds)
-                    },
-                    statusBarStateController,
-                    shadeExpansionStateManager,
-                    statusBarKeyguardViewManager,
-                    keyguardUpdateMonitor,
-                    dumpManager,
-                    transitionController,
-                    configurationController,
-                    keyguardStateController,
-                    unlockedScreenOffAnimationController,
-                    dialogManager,
-                    controller,
-                    activityLaunchAnimator,
-                    featureFlags,
-                    primaryBouncerInteractor,
-                    alternateBouncerInteractor,
-                    udfpsKeyguardAccessibilityDelegate,
-                )
+                if (featureFlags.isEnabled(REFACTOR_UDFPS_KEYGUARD_VIEWS)) {
+                    udfpsKeyguardViewModels.get().setSensorBounds(sensorBounds)
+                    UdfpsKeyguardViewController(
+                        view.addUdfpsView(R.layout.udfps_keyguard_view),
+                        statusBarStateController,
+                        shadeExpansionStateManager,
+                        dialogManager,
+                        dumpManager,
+                        alternateBouncerInteractor,
+                        udfpsKeyguardViewModels.get(),
+                    )
+                } else {
+                    UdfpsKeyguardViewControllerLegacy(
+                        view.addUdfpsView(R.layout.udfps_keyguard_view_legacy) {
+                            updateSensorLocation(sensorBounds)
+                        },
+                        statusBarStateController,
+                        shadeExpansionStateManager,
+                        statusBarKeyguardViewManager,
+                        keyguardUpdateMonitor,
+                        dumpManager,
+                        transitionController,
+                        configurationController,
+                        keyguardStateController,
+                        unlockedScreenOffAnimationController,
+                        dialogManager,
+                        controller,
+                        activityLaunchAnimator,
+                        featureFlags,
+                        primaryBouncerInteractor,
+                        alternateBouncerInteractor,
+                        udfpsKeyguardAccessibilityDelegate,
+                    )
+                }
             }
             REASON_AUTH_BP -> {
                 // note: empty controller, currently shows no visual affordance
@@ -415,7 +436,7 @@
     }
 
     private fun shouldRotate(animation: UdfpsAnimationViewController<*>?): Boolean {
-        if (animation !is UdfpsKeyguardViewControllerLegacy) {
+        if (animation !is UdfpsKeyguardViewControllerAdapter) {
             // always rotate view if we're not on the keyguard
             return true
         }
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardView.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardView.kt
new file mode 100644
index 0000000..8cc15da
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardView.kt
@@ -0,0 +1,58 @@
+/*
+ * 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.biometrics
+
+import android.content.Context
+import android.util.AttributeSet
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+
+/** View corresponding with udfps_keyguard_view.xml */
+@ExperimentalCoroutinesApi
+class UdfpsKeyguardView(
+    context: Context,
+    attrs: AttributeSet?,
+) :
+    UdfpsAnimationView(
+        context,
+        attrs,
+    ) {
+    private val fingerprintDrawablePlaceHolder = UdfpsFpDrawable(context)
+    private var visible = false
+
+    override fun calculateAlpha(): Int {
+        return if (mPauseAuth) {
+            0
+        } else 255 // ViewModels handle animating alpha values
+    }
+
+    override fun getDrawable(): UdfpsDrawable {
+        return fingerprintDrawablePlaceHolder
+    }
+
+    fun useExpandedOverlay(useExpandedOverlay: Boolean) {
+        mUseExpandedOverlay = useExpandedOverlay
+    }
+
+    fun isVisible(): Boolean {
+        return visible
+    }
+
+    fun setVisible(isVisible: Boolean) {
+        visible = isVisible
+        isPauseAuth = !visible
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerLegacy.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerLegacy.kt
index 9bafeec..15bd731 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerLegacy.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerLegacy.kt
@@ -34,6 +34,7 @@
 import com.android.systemui.dump.DumpManager
 import com.android.systemui.flags.FeatureFlags
 import com.android.systemui.flags.Flags
+import com.android.systemui.keyguard.ui.adapter.UdfpsKeyguardViewControllerAdapter
 import com.android.systemui.lifecycle.repeatWhenAttached
 import com.android.systemui.plugins.statusbar.StatusBarStateController
 import com.android.systemui.shade.ShadeExpansionListener
@@ -80,7 +81,8 @@
         shadeExpansionStateManager,
         systemUIDialogManager,
         dumpManager,
-    ) {
+    ),
+    UdfpsKeyguardViewControllerAdapter {
     private val useExpandedOverlay: Boolean =
         featureFlags.isEnabled(Flags.UDFPS_NEW_TOUCH_DETECTION)
     private var showingUdfpsBouncer = false
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/controller/UdfpsKeyguardViewController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/controller/UdfpsKeyguardViewController.kt
new file mode 100644
index 0000000..2a9f3ea
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/controller/UdfpsKeyguardViewController.kt
@@ -0,0 +1,77 @@
+/*
+ * 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.biometrics.ui.controller
+
+import com.android.systemui.biometrics.UdfpsAnimationViewController
+import com.android.systemui.biometrics.UdfpsKeyguardView
+import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor
+import com.android.systemui.dump.DumpManager
+import com.android.systemui.keyguard.ui.adapter.UdfpsKeyguardViewControllerAdapter
+import com.android.systemui.keyguard.ui.viewmodel.UdfpsKeyguardViewModels
+import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.shade.ShadeExpansionStateManager
+import com.android.systemui.statusbar.phone.SystemUIDialogManager
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+
+/** Class that coordinates non-HBM animations during keyguard authentication. */
+@ExperimentalCoroutinesApi
+open class UdfpsKeyguardViewController(
+    val view: UdfpsKeyguardView,
+    statusBarStateController: StatusBarStateController,
+    shadeExpansionStateManager: ShadeExpansionStateManager,
+    systemUIDialogManager: SystemUIDialogManager,
+    dumpManager: DumpManager,
+    private val alternateBouncerInteractor: AlternateBouncerInteractor,
+    udfpsKeyguardViewModels: UdfpsKeyguardViewModels,
+) :
+    UdfpsAnimationViewController<UdfpsKeyguardView>(
+        view,
+        statusBarStateController,
+        shadeExpansionStateManager,
+        systemUIDialogManager,
+        dumpManager,
+    ),
+    UdfpsKeyguardViewControllerAdapter {
+    override val tag: String
+        get() = TAG
+
+    init {
+        udfpsKeyguardViewModels.bindViews(view)
+    }
+
+    public override fun onViewAttached() {
+        super.onViewAttached()
+        alternateBouncerInteractor.setAlternateBouncerUIAvailable(true)
+    }
+
+    public override fun onViewDetached() {
+        super.onViewDetached()
+        alternateBouncerInteractor.setAlternateBouncerUIAvailable(false)
+    }
+
+    override fun shouldPauseAuth(): Boolean {
+        return !view.isVisible()
+    }
+
+    override fun listenForTouchesOutsideView(): Boolean {
+        return true
+    }
+
+    companion object {
+        private const val TAG = "UdfpsKeyguardViewController"
+    }
+}
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 b293ea6..db6ca0b 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
@@ -61,6 +61,7 @@
 
     private val pin: PinBouncerViewModel by lazy {
         PinBouncerViewModel(
+            applicationContext = applicationContext,
             applicationScope = applicationScope,
             interactor = interactor,
             isInputEnabled = isInputEnabled,
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 014ebc3..641e863 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
@@ -16,6 +16,8 @@
 
 package com.android.systemui.bouncer.ui.viewmodel
 
+import android.content.Context
+import com.android.keyguard.PinShapeAdapter
 import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
 import com.android.systemui.bouncer.domain.interactor.BouncerInteractor
 import kotlinx.coroutines.CoroutineScope
@@ -29,6 +31,7 @@
 
 /** Holds UI state and handles user input for the PIN code bouncer UI. */
 class PinBouncerViewModel(
+    applicationContext: Context,
     private val applicationScope: CoroutineScope,
     private val interactor: BouncerInteractor,
     isInputEnabled: StateFlow<Boolean>,
@@ -37,6 +40,8 @@
         isInputEnabled = isInputEnabled,
     ) {
 
+    val pinShapes = PinShapeAdapter(applicationContext)
+
     private val mutablePinEntries = MutableStateFlow<List<EnteredKey>>(emptyList())
     val pinEntries: StateFlow<List<EnteredKey>> = mutablePinEntries
 
diff --git a/packages/SystemUI/src/com/android/systemui/common/ui/view/SeekBarWithIconButtonsView.java b/packages/SystemUI/src/com/android/systemui/common/ui/view/SeekBarWithIconButtonsView.java
index 4538a6c..277b427 100644
--- a/packages/SystemUI/src/com/android/systemui/common/ui/view/SeekBarWithIconButtonsView.java
+++ b/packages/SystemUI/src/com/android/systemui/common/ui/view/SeekBarWithIconButtonsView.java
@@ -16,6 +16,7 @@
 
 package com.android.systemui.common.ui.view;
 
+import android.annotation.IntDef;
 import android.annotation.Nullable;
 import android.content.Context;
 import android.content.res.TypedArray;
@@ -30,6 +31,9 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.systemui.R;
 
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
 /**
  * The layout contains a seekbar whose progress could be modified
  * through the icons on two ends of the seekbar.
@@ -47,6 +51,8 @@
     private SeekBar mSeekbar;
     private int mSeekBarChangeMagnitude = 1;
 
+    private boolean mSetProgressFromButtonFlag = false;
+
     private SeekBarChangeListener mSeekBarListener = new SeekBarChangeListener();
     private String[] mStateLabels = null;
 
@@ -121,21 +127,8 @@
 
         mSeekbar.setOnSeekBarChangeListener(mSeekBarListener);
 
-        mIconStartFrame.setOnClickListener((view) -> {
-            final int progress = mSeekbar.getProgress();
-            if (progress > 0) {
-                mSeekbar.setProgress(progress - mSeekBarChangeMagnitude);
-                setIconViewAndFrameEnabled(mIconStart, mSeekbar.getProgress() > 0);
-            }
-        });
-
-        mIconEndFrame.setOnClickListener((view) -> {
-            final int progress = mSeekbar.getProgress();
-            if (progress < mSeekbar.getMax()) {
-                mSeekbar.setProgress(progress + mSeekBarChangeMagnitude);
-                setIconViewAndFrameEnabled(mIconEnd, mSeekbar.getProgress() < mSeekbar.getMax());
-            }
-        });
+        mIconStartFrame.setOnClickListener((view) -> onIconStartClicked());
+        mIconEndFrame.setOnClickListener((view) -> onIconEndClicked());
     }
 
     private static void setIconViewAndFrameEnabled(View iconView, boolean enabled) {
@@ -172,9 +165,9 @@
      * Sets a onSeekbarChangeListener to the seekbar in the layout.
      * We update the Start Icon and End Icon if needed when the seekbar progress is changed.
      */
-    public void setOnSeekBarChangeListener(
-            @Nullable SeekBar.OnSeekBarChangeListener onSeekBarChangeListener) {
-        mSeekBarListener.setOnSeekBarChangeListener(onSeekBarChangeListener);
+    public void setOnSeekBarWithIconButtonsChangeListener(
+            @Nullable OnSeekBarWithIconButtonsChangeListener onSeekBarChangeListener) {
+        mSeekBarListener.setOnSeekBarWithIconButtonsChangeListener(onSeekBarChangeListener);
     }
 
     /**
@@ -216,16 +209,72 @@
      */
     public void setProgress(int progress) {
         mSeekbar.setProgress(progress);
-        updateIconViewIfNeeded(progress);
+        updateIconViewIfNeeded(mSeekbar.getProgress());
     }
 
+    private void setProgressFromButton(int progress) {
+        mSetProgressFromButtonFlag = true;
+        mSeekbar.setProgress(progress);
+        updateIconViewIfNeeded(mSeekbar.getProgress());
+    }
+
+    private void onIconStartClicked() {
+        final int progress = mSeekbar.getProgress();
+        if (progress > 0) {
+            setProgressFromButton(progress - mSeekBarChangeMagnitude);
+        }
+    }
+
+    private void onIconEndClicked() {
+        final int progress = mSeekbar.getProgress();
+        if (progress < mSeekbar.getMax()) {
+            setProgressFromButton(progress + mSeekBarChangeMagnitude);
+        }
+    }
+
+    /**
+     * Get current seekbar progress
+     *
+     * @return
+     */
     @VisibleForTesting
     public int getProgress() {
         return mSeekbar.getProgress();
     }
 
+    /**
+     * Extended from {@link SeekBar.OnSeekBarChangeListener} to add callback to notify the listeners
+     * the user interaction with the SeekBarWithIconButtonsView is finalized.
+     */
+    public interface OnSeekBarWithIconButtonsChangeListener
+            extends SeekBar.OnSeekBarChangeListener {
+
+        @Retention(RetentionPolicy.SOURCE)
+        @IntDef({
+                ControlUnitType.SLIDER,
+                ControlUnitType.BUTTON
+        })
+        /** Denotes the Last user interacted control unit type. */
+        @interface ControlUnitType {
+            int SLIDER = 0;
+            int BUTTON = 1;
+        }
+
+        /**
+         * Notification that the user interaction with SeekBarWithIconButtonsView is finalized. This
+         * would be triggered after user ends dragging on the slider or clicks icon buttons.
+         *
+         * @param seekBar The SeekBar in which the user ends interaction with
+         * @param control The last user interacted control unit. It would be
+         *                {@link ControlUnitType#SLIDER} if the user was changing the seekbar
+         *                progress through dragging the slider, or {@link ControlUnitType#BUTTON}
+         *                is the user was clicking button to change the progress.
+         */
+        void onUserInteractionFinalized(SeekBar seekBar, @ControlUnitType int control);
+    }
+
     private class SeekBarChangeListener implements SeekBar.OnSeekBarChangeListener {
-        private SeekBar.OnSeekBarChangeListener mOnSeekBarChangeListener = null;
+        private OnSeekBarWithIconButtonsChangeListener mOnSeekBarChangeListener = null;
 
         @Override
         public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
@@ -233,7 +282,17 @@
                 setSeekbarStateDescription();
             }
             if (mOnSeekBarChangeListener != null) {
-                mOnSeekBarChangeListener.onProgressChanged(seekBar, progress, fromUser);
+                if (mSetProgressFromButtonFlag) {
+                    mSetProgressFromButtonFlag = false;
+                    mOnSeekBarChangeListener.onProgressChanged(
+                            seekBar, progress, /* fromUser= */ true);
+                    // Directly trigger onUserInteractionFinalized since the interaction
+                    // (click button) is ended.
+                    mOnSeekBarChangeListener.onUserInteractionFinalized(
+                            seekBar, OnSeekBarWithIconButtonsChangeListener.ControlUnitType.BUTTON);
+                } else {
+                    mOnSeekBarChangeListener.onProgressChanged(seekBar, progress, fromUser);
+                }
             }
             updateIconViewIfNeeded(progress);
         }
@@ -249,10 +308,13 @@
         public void onStopTrackingTouch(SeekBar seekBar) {
             if (mOnSeekBarChangeListener != null) {
                 mOnSeekBarChangeListener.onStopTrackingTouch(seekBar);
+                mOnSeekBarChangeListener.onUserInteractionFinalized(
+                        seekBar, OnSeekBarWithIconButtonsChangeListener.ControlUnitType.SLIDER);
             }
         }
 
-        void setOnSeekBarChangeListener(SeekBar.OnSeekBarChangeListener listener) {
+        void setOnSeekBarWithIconButtonsChangeListener(
+                OnSeekBarWithIconButtonsChangeListener listener) {
             mOnSeekBarChangeListener = listener;
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt b/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt
index 76002d3..b1f513d 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt
@@ -42,6 +42,7 @@
 import com.android.systemui.media.taptotransfer.MediaTttCommandLineHelper
 import com.android.systemui.media.taptotransfer.receiver.MediaTttChipControllerReceiver
 import com.android.systemui.media.taptotransfer.sender.MediaTttSenderCoordinator
+import com.android.systemui.mediaprojection.taskswitcher.MediaProjectionTaskSwitcherCoreStartable
 import com.android.systemui.power.PowerUI
 import com.android.systemui.reardisplay.RearDisplayDialogController
 import com.android.systemui.recents.Recents
@@ -49,6 +50,7 @@
 import com.android.systemui.shortcut.ShortcutKeyDispatcher
 import com.android.systemui.statusbar.notification.InstantAppNotifier
 import com.android.systemui.statusbar.phone.KeyguardLiftController
+import com.android.systemui.statusbar.phone.LockscreenWallpaper
 import com.android.systemui.stylus.StylusUsiPowerStartable
 import com.android.systemui.temporarydisplay.chipbar.ChipbarCoordinator
 import com.android.systemui.theme.ThemeOverlayController
@@ -110,6 +112,14 @@
     @ClassKey(KeyboardUI::class)
     abstract fun bindKeyboardUI(sysui: KeyboardUI): CoreStartable
 
+    /** Inject into MediaProjectionTaskSwitcherCoreStartable. */
+    @Binds
+    @IntoMap
+    @ClassKey(MediaProjectionTaskSwitcherCoreStartable::class)
+    abstract fun bindProjectedTaskListener(
+            sysui: MediaProjectionTaskSwitcherCoreStartable
+    ): CoreStartable
+
     /** Inject into KeyguardBiometricLockoutLogger */
     @Binds
     @IntoMap
@@ -301,4 +311,9 @@
     @IntoMap
     @ClassKey(KeyguardViewConfigurator::class)
     abstract fun bindKeyguardViewConfigurator(impl: KeyguardViewConfigurator): CoreStartable
+
+    @Binds
+    @IntoMap
+    @ClassKey(LockscreenWallpaper::class)
+    abstract fun bindLockscreenWallpaper(impl: LockscreenWallpaper): CoreStartable
 }
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
index 8f3c3d6..3b89739 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
@@ -54,10 +54,12 @@
 import com.android.systemui.flags.FeatureFlags;
 import com.android.systemui.flags.FlagsModule;
 import com.android.systemui.keyboard.KeyboardModule;
+import com.android.systemui.keyguard.ui.view.layout.LockscreenLayoutModule;
 import com.android.systemui.log.dagger.LogModule;
 import com.android.systemui.log.dagger.MonitorLog;
 import com.android.systemui.log.table.TableLogBuffer;
 import com.android.systemui.mediaprojection.appselector.MediaProjectionModule;
+import com.android.systemui.mediaprojection.taskswitcher.MediaProjectionTaskSwitcherModule;
 import com.android.systemui.model.SysUiState;
 import com.android.systemui.motiontool.MotionToolModule;
 import com.android.systemui.navigationbar.NavigationBarComponent;
@@ -178,8 +180,10 @@
             GarbageMonitorModule.class,
             KeyboardModule.class,
             LetterboxModule.class,
+            LockscreenLayoutModule.class,
             LogModule.class,
             MediaProjectionModule.class,
+            MediaProjectionTaskSwitcherModule.class,
             MotionToolModule.class,
             PeopleHubModule.class,
             PeopleModule.class,
diff --git a/packages/SystemUI/src/com/android/systemui/decor/DebugRoundedCornerDelegate.kt b/packages/SystemUI/src/com/android/systemui/decor/DebugRoundedCornerDelegate.kt
index 4069bc7d..5571687 100644
--- a/packages/SystemUI/src/com/android/systemui/decor/DebugRoundedCornerDelegate.kt
+++ b/packages/SystemUI/src/com/android/systemui/decor/DebugRoundedCornerDelegate.kt
@@ -77,16 +77,30 @@
     }
 
     fun applyNewDebugCorners(
-        topCorner: DebugRoundedCornerModel,
-        bottomCorner: DebugRoundedCornerModel,
+        topCorner: DebugRoundedCornerModel?,
+        bottomCorner: DebugRoundedCornerModel?,
     ) {
-        hasTop = true
-        topRoundedDrawable = topCorner.toPathDrawable(paint)
-        topRoundedSize = topCorner.size()
+        topCorner?.let {
+            hasTop = true
+            topRoundedDrawable = it.toPathDrawable(paint)
+            topRoundedSize = it.size()
+        }
+            ?: {
+                hasTop = false
+                topRoundedDrawable = null
+                topRoundedSize = Size(0, 0)
+            }
 
-        hasBottom = true
-        bottomRoundedDrawable = bottomCorner.toPathDrawable(paint)
-        bottomRoundedSize = bottomCorner.size()
+        bottomCorner?.let {
+            hasBottom = true
+            bottomRoundedDrawable = it.toPathDrawable(paint)
+            bottomRoundedSize = it.size()
+        }
+            ?: {
+                hasBottom = false
+                bottomRoundedDrawable = null
+                bottomRoundedSize = Size(0, 0)
+            }
     }
 
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/decor/ScreenDecorCommand.kt b/packages/SystemUI/src/com/android/systemui/decor/ScreenDecorCommand.kt
new file mode 100644
index 0000000..fa1d898
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/decor/ScreenDecorCommand.kt
@@ -0,0 +1,171 @@
+/*
+ * 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.decor
+
+import android.graphics.Color
+import android.graphics.Path
+import android.util.PathParser
+import com.android.systemui.statusbar.commandline.ParseableCommand
+import com.android.systemui.statusbar.commandline.Type
+import com.android.systemui.statusbar.commandline.map
+import java.io.PrintWriter
+
+/** Debug screen-decor command to be handled by the SystemUI command line interface */
+class ScreenDecorCommand(
+    private val callback: Callback,
+) : ParseableCommand(SCREEN_DECOR_CMD_NAME) {
+    val debug: Boolean? by
+        param(
+            longName = "debug",
+            description =
+                "Enter or exits debug mode. Effectively makes the corners visible and allows " +
+                    "for overriding the path data for the anti-aliasing corner paths and display " +
+                    "cutout.",
+            valueParser = Type.Boolean,
+        )
+
+    val color: Int? by
+        param(
+            longName = "color",
+            shortName = "c",
+            description =
+                "Set a specific color for the debug assets. See Color#parseString() for " +
+                    "accepted inputs.",
+            valueParser = Type.String.map { it.toColorIntOrNull() }
+        )
+
+    val roundedTop: RoundedCornerSubCommand? by subCommand(RoundedCornerSubCommand("rounded-top"))
+
+    val roundedBottom: RoundedCornerSubCommand? by
+        subCommand(RoundedCornerSubCommand("rounded-bottom"))
+
+    override fun execute(pw: PrintWriter) {
+        callback.onExecute(this, pw)
+    }
+
+    override fun toString(): String {
+        return "ScreenDecorCommand(" +
+            "debug=$debug, " +
+            "color=$color, " +
+            "roundedTop=$roundedTop, " +
+            "roundedBottom=$roundedBottom)"
+    }
+
+    /** For use in ScreenDecorations.java, define a Callback */
+    interface Callback {
+        fun onExecute(cmd: ScreenDecorCommand, pw: PrintWriter)
+    }
+
+    companion object {
+        const val SCREEN_DECOR_CMD_NAME = "screen-decor"
+    }
+}
+
+/**
+ * Defines a subcommand suitable for `rounded-top` and `rounded-bottom`. They both have the same
+ * API.
+ */
+class RoundedCornerSubCommand(name: String) : ParseableCommand(name) {
+    val height by
+        param(
+                longName = "height",
+                description = "The height of a corner, in pixels.",
+                valueParser = Type.Int,
+            )
+            .required()
+
+    val width by
+        param(
+                longName = "width",
+                description =
+                    "The width of the corner, in pixels. Likely should be equal to the height.",
+                valueParser = Type.Int,
+            )
+            .required()
+
+    val pathData by
+        param(
+                longName = "path-data",
+                shortName = "d",
+                description =
+                    "PathParser-compatible path string to be rendered as the corner drawable. " +
+                        "This path should be a closed arc oriented as the top-left corner " +
+                        "of the device",
+                valueParser = Type.String.map { it.toPathOrNull() }
+            )
+            .required()
+
+    val viewportHeight: Float? by
+        param(
+            longName = "viewport-height",
+            description =
+                "The height of the viewport for the given path string. " +
+                    "If null, the corner height will be used.",
+            valueParser = Type.Float,
+        )
+
+    val scaleY: Float
+        get() = viewportHeight?.let { height.toFloat() / it } ?: 1.0f
+
+    val viewportWidth: Float? by
+        param(
+            longName = "viewport-width",
+            description =
+                "The width of the viewport for the given path string. " +
+                    "If null, the corner width will be used.",
+            valueParser = Type.Float,
+        )
+
+    val scaleX: Float
+        get() = viewportWidth?.let { width.toFloat() / it } ?: 1.0f
+
+    override fun execute(pw: PrintWriter) {
+        // Not needed for a subcommand
+    }
+
+    override fun toString(): String {
+        return "RoundedCornerSubCommand(" +
+            "height=$height," +
+            " width=$width," +
+            " pathData='$pathData'," +
+            " viewportHeight=$viewportHeight," +
+            " viewportWidth=$viewportWidth)"
+    }
+
+    fun toRoundedCornerDebugModel(): DebugRoundedCornerModel =
+        DebugRoundedCornerModel(
+            path = pathData,
+            width = width,
+            height = height,
+            scaleX = scaleX,
+            scaleY = scaleY,
+        )
+}
+
+fun String.toPathOrNull(): Path? =
+    try {
+        PathParser.createPathFromPathData(this)
+    } catch (e: Exception) {
+        null
+    }
+
+fun String.toColorIntOrNull(): Int? =
+    try {
+        Color.parseColor(this)
+    } catch (e: Exception) {
+        null
+    }
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
index db5f546..cf26b1c 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
@@ -89,7 +89,16 @@
     // TODO(b/277338665): Tracking Bug
     @JvmField
     val NOTIFICATION_SHELF_REFACTOR =
-        unreleasedFlag(271161129, "notification_shelf_refactor")
+        unreleasedFlag(271161129, "notification_shelf_refactor", teamfood = true)
+
+    // TODO(b/288326013): Tracking Bug
+    @JvmField
+    val NOTIFICATION_ASYNC_HYBRID_VIEW_INFLATION =
+            unreleasedFlag(
+                    288326013,
+                    "notification_async_hybrid_view_inflation",
+                    teamfood = false
+            )
 
     @JvmField
     val ANIMATED_NOTIFICATION_SHADE_INSETS =
@@ -137,6 +146,14 @@
     val LOCKSCREEN_WITHOUT_SECURE_LOCK_WHEN_DREAMING = releasedFlag(208,
         "lockscreen_without_secure_lock_when_dreaming")
 
+    // TODO(b/286092087): Tracking Bug
+    @JvmField
+    val ENABLE_SYSTEM_UI_DREAM_CONTROLLER = unreleasedFlag(301, "enable_system_ui_dream_controller")
+
+    // TODO(b/288287730): Tracking Bug
+    @JvmField
+    val ENABLE_SYSTEM_UI_DREAM_HOSTING = unreleasedFlag(302, "enable_system_ui_dream_hosting")
+
     /**
      * Whether the clock on a wide lock screen should use the new "stepping" animation for moving
      * the digits when the clock moves.
@@ -251,11 +268,6 @@
     @JvmField
     val MIGRATE_INDICATION_AREA = unreleasedFlag(236, "migrate_indication_area", teamfood = true)
 
-    /** Migrate the lock icon view to the new keyguard root view. */
-    // TODO(b/286552209): Tracking bug.
-    @JvmField
-    val MIGRATE_LOCK_ICON = unreleasedFlag(238, "migrate_lock_icon")
-
     /** Whether to listen for fingerprint authentication over keyguard occluding activities. */
     // TODO(b/283260512): Tracking bug.
     @JvmField
@@ -270,6 +282,11 @@
     @JvmField
     val TRANSIT_CLOCK = unreleasedFlag(239, "lockscreen_custom_transit_clock")
 
+    /** Migrate the lock icon view to the new keyguard root view. */
+    // TODO(b/286552209): Tracking bug.
+    @JvmField
+    val MIGRATE_LOCK_ICON = unreleasedFlag(240, "migrate_lock_icon")
+
     // 300 - power menu
     // TODO(b/254512600): Tracking Bug
     @JvmField val POWER_MENU_LITE = releasedFlag(300, "power_menu_lite")
@@ -308,7 +325,7 @@
         )
 
     @JvmField
-    val QS_PIPELINE_NEW_HOST = releasedFlag(504, "qs_pipeline_new_host")
+    val QS_PIPELINE_NEW_HOST = unreleasedFlag(504, "qs_pipeline_new_host", teamfood = true)
 
     // TODO(b/278068252): Tracking Bug
     @JvmField
@@ -550,6 +567,12 @@
     val WALLPAPER_MULTI_CROP =
         sysPropBooleanFlag(1118, "persist.wm.debug.wallpaper_multi_crop", default = false)
 
+    // TODO(b/290220798): Tracking Bug
+    @Keep
+    @JvmField
+    val ENABLE_PIP2_IMPLEMENTATION =
+        sysPropBooleanFlag(1119, "persist.wm.debug.enable_pip2_implementation", default = false)
+
 
     // 1200 - predictive back
     @Keep
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardSliceProvider.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardSliceProvider.java
index 0511314..732102e 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardSliceProvider.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardSliceProvider.java
@@ -50,6 +50,7 @@
 import com.android.keyguard.KeyguardUpdateMonitorCallback;
 import com.android.systemui.R;
 import com.android.systemui.SystemUIAppComponentFactoryBase;
+import com.android.systemui.dagger.qualifiers.Background;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.settings.UserTracker;
 import com.android.systemui.statusbar.NotificationMediaManager;
@@ -151,6 +152,9 @@
     private SystemUIAppComponentFactoryBase.ContextAvailableCallback mContextAvailableCallback;
     @Inject
     WakeLockLogger mWakeLockLogger;
+    @Inject
+    @Background
+    Handler mBgHandler;
 
     /**
      * Receiver responsible for time ticking and updating the date format.
@@ -502,7 +506,7 @@
     }
 
     protected void notifyChange() {
-        mContentResolver.notifyChange(mSliceUri, null /* observer */);
+        mBgHandler.post(() -> mContentResolver.notifyChange(mSliceUri, null /* observer */));
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt
index df83aaf..f59ad90 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt
@@ -26,6 +26,8 @@
 import com.android.systemui.flags.Flags
 import com.android.systemui.keyguard.ui.binder.KeyguardIndicationAreaBinder
 import com.android.systemui.keyguard.ui.view.KeyguardRootView
+import com.android.systemui.keyguard.ui.view.layout.KeyguardLayoutManager
+import com.android.systemui.keyguard.ui.view.layout.KeyguardLayoutManagerCommandListener
 import com.android.systemui.keyguard.ui.viewmodel.KeyguardIndicationAreaViewModel
 import com.android.systemui.shade.NotificationShadeWindowView
 import com.android.systemui.statusbar.KeyguardIndicationController
@@ -42,6 +44,8 @@
     private val notificationShadeWindowView: NotificationShadeWindowView,
     private val featureFlags: FeatureFlags,
     private val indicationController: KeyguardIndicationController,
+    private val keyguardLayoutManager: KeyguardLayoutManager,
+    private val keyguardLayoutManagerCommandListener: KeyguardLayoutManagerCommandListener,
 ) : CoreStartable {
 
     private var indicationAreaHandle: DisposableHandle? = null
@@ -51,6 +55,8 @@
             notificationShadeWindowView.requireViewById(R.id.notification_panel) as ViewGroup
         bindIndicationArea(notificationPanel)
         bindLockIconView(notificationPanel)
+        keyguardLayoutManager.layoutViews()
+        keyguardLayoutManagerCommandListener.start()
     }
 
     fun bindIndicationArea(legacyParent: ViewGroup) {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index 155e023..468d760 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -140,6 +140,7 @@
 import com.android.systemui.flags.Flags;
 import com.android.systemui.flags.SystemPropertiesHelper;
 import com.android.systemui.keyguard.dagger.KeyguardModule;
+import com.android.systemui.keyguard.shared.model.TransitionStep;
 import com.android.systemui.keyguard.ui.viewmodel.DreamingToLockscreenTransitionViewModel;
 import com.android.systemui.log.SessionTracker;
 import com.android.systemui.navigationbar.NavigationModeController;
@@ -545,6 +546,8 @@
 
     private CentralSurfaces mCentralSurfaces;
 
+    private IRemoteAnimationFinishedCallback mUnoccludeFromDreamFinishedCallback;
+
     private final DeviceConfig.OnPropertiesChangedListener mOnPropertiesChangedListener =
             new DeviceConfig.OnPropertiesChangedListener() {
             @Override
@@ -582,17 +585,9 @@
         @Override
         public void onUserSwitching(int userId) {
             if (DEBUG) Log.d(TAG, String.format("onUserSwitching %d", userId));
-            // Note that the mLockPatternUtils user has already been updated from setCurrentUser.
-            // We need to force a reset of the views, since lockNow (called by
-            // ActivityManagerService) will not reconstruct the keyguard if it is already showing.
             synchronized (KeyguardViewMediator.this) {
                 resetKeyguardDonePendingLocked();
-                if (mLockPatternUtils.isLockScreenDisabled(userId)) {
-                    // If we are switching to a user that has keyguard disabled, dismiss keyguard.
-                    dismiss(null /* callback */, null /* message */);
-                } else {
-                    resetStateLocked();
-                }
+                dismiss(null /* callback */, null /* message */);
                 adjustStatusBarLocked();
             }
         }
@@ -600,16 +595,9 @@
         @Override
         public void onUserSwitchComplete(int userId) {
             if (DEBUG) Log.d(TAG, String.format("onUserSwitchComplete %d", userId));
-            if (userId != UserHandle.USER_SYSTEM) {
-                UserInfo info = UserManager.get(mContext).getUserInfo(userId);
-                // Don't try to dismiss if the user has Pin/Pattern/Password set
-                if (info == null || mLockPatternUtils.isSecure(userId)) {
-                    return;
-                } else if (info.isGuest() || info.isDemo()) {
-                    // If we just switched to a guest, try to dismiss keyguard.
-                    dismiss(null /* callback */, null /* message */);
-                }
-            }
+            // We are calling dismiss again and with a delay as there are race conditions
+            // in some scenarios caused by async layout listeners
+            mHandler.postDelayed(() -> dismiss(null /* callback */, null /* message */), 500);
         }
 
         @Override
@@ -649,6 +637,8 @@
             switch (simState) {
                 case TelephonyManager.SIM_STATE_NOT_READY:
                 case TelephonyManager.SIM_STATE_ABSENT:
+                case TelephonyManager.SIM_STATE_UNKNOWN:
+                    mPendingPinLock = false;
                     // only force lock screen in case of missing sim if user hasn't
                     // gone through setup wizard
                     synchronized (KeyguardViewMediator.this) {
@@ -713,9 +703,6 @@
                         }
                     }
                     break;
-                case TelephonyManager.SIM_STATE_UNKNOWN:
-                    mPendingPinLock = false;
-                    break;
                 default:
                     if (DEBUG_SIM_STATES) Log.v(TAG, "Unspecific state: " + simState);
                     break;
@@ -1177,6 +1164,7 @@
                             getRemoteSurfaceAlphaApplier().accept(0.0f);
                             mDreamingToLockscreenTransitionViewModel.get()
                                     .startTransition();
+                            mUnoccludeFromDreamFinishedCallback = finishedCallback;
                             return;
                         }
 
@@ -1256,6 +1244,19 @@
         };
     }
 
+    private Consumer<TransitionStep> getFinishedCallbackConsumer() {
+        return (TransitionStep step) -> {
+            if (mUnoccludeFromDreamFinishedCallback == null) return;
+            try {
+                mUnoccludeFromDreamFinishedCallback.onAnimationFinished();
+                mUnoccludeFromDreamFinishedCallback = null;
+            } catch (RemoteException e) {
+                Log.e(TAG, "Wasn't able to callback", e);
+            }
+            mInteractionJankMonitor.end(CUJ_LOCKSCREEN_OCCLUSION);
+        };
+    }
+
     private DeviceConfigProxy mDeviceConfig;
     private DozeParameters mDozeParameters;
 
@@ -1521,6 +1522,9 @@
                 collectFlow(viewRootImpl.getView(),
                         mDreamingToLockscreenTransitionViewModel.get().getDreamOverlayAlpha(),
                         getRemoteSurfaceAlphaApplier(), mMainDispatcher);
+                collectFlow(viewRootImpl.getView(),
+                        mDreamingToLockscreenTransitionViewModel.get().getTransitionEnded(),
+                        getFinishedCallbackConsumer(), mMainDispatcher);
             }
         }
         // Most services aren't available until the system reaches the ready state, so we
@@ -2383,58 +2387,72 @@
     private Handler mHandler = new Handler(Looper.myLooper(), null, true /*async*/) {
         @Override
         public void handleMessage(Message msg) {
+            String message = "";
             switch (msg.what) {
                 case SHOW:
+                    message = "SHOW";
                     handleShow((Bundle) msg.obj);
                     break;
                 case HIDE:
+                    message = "HIDE";
                     handleHide();
                     break;
                 case RESET:
+                    message = "RESET";
                     handleReset(msg.arg1 != 0);
                     break;
                 case VERIFY_UNLOCK:
+                    message = "VERIFY_UNLOCK";
                     Trace.beginSection("KeyguardViewMediator#handleMessage VERIFY_UNLOCK");
                     handleVerifyUnlock();
                     Trace.endSection();
                     break;
                 case NOTIFY_STARTED_GOING_TO_SLEEP:
+                    message = "NOTIFY_STARTED_GOING_TO_SLEEP";
                     handleNotifyStartedGoingToSleep();
                     break;
                 case NOTIFY_FINISHED_GOING_TO_SLEEP:
+                    message = "NOTIFY_FINISHED_GOING_TO_SLEEP";
                     handleNotifyFinishedGoingToSleep();
                     break;
                 case NOTIFY_STARTED_WAKING_UP:
+                    message = "NOTIFY_STARTED_WAKING_UP";
                     Trace.beginSection(
                             "KeyguardViewMediator#handleMessage NOTIFY_STARTED_WAKING_UP");
                     handleNotifyStartedWakingUp();
                     Trace.endSection();
                     break;
                 case KEYGUARD_DONE:
+                    message = "KEYGUARD_DONE";
                     Trace.beginSection("KeyguardViewMediator#handleMessage KEYGUARD_DONE");
                     handleKeyguardDone();
                     Trace.endSection();
                     break;
                 case KEYGUARD_DONE_DRAWING:
+                    message = "KEYGUARD_DONE_DRAWING";
                     Trace.beginSection("KeyguardViewMediator#handleMessage KEYGUARD_DONE_DRAWING");
                     handleKeyguardDoneDrawing();
                     Trace.endSection();
                     break;
                 case SET_OCCLUDED:
+                    message = "SET_OCCLUDED";
                     Trace.beginSection("KeyguardViewMediator#handleMessage SET_OCCLUDED");
                     handleSetOccluded(msg.arg1 != 0, msg.arg2 != 0);
                     Trace.endSection();
                     break;
                 case KEYGUARD_TIMEOUT:
+                    message = "KEYGUARD_TIMEOUT";
                     synchronized (KeyguardViewMediator.this) {
                         doKeyguardLocked((Bundle) msg.obj);
                     }
                     break;
                 case DISMISS:
-                    final DismissMessage message = (DismissMessage) msg.obj;
-                    handleDismiss(message.getCallback(), message.getMessage());
+                    message = "DISMISS";
+                    final DismissMessage dismissMsg = (DismissMessage) msg.obj;
+                    handleDismiss(dismissMsg.getCallback(), dismissMsg.getMessage());
                     break;
                 case START_KEYGUARD_EXIT_ANIM:
+                    message = "START_KEYGUARD_EXIT_ANIM";
                     Trace.beginSection(
                             "KeyguardViewMediator#handleMessage START_KEYGUARD_EXIT_ANIM");
                     synchronized (KeyguardViewMediator.this) {
@@ -2452,21 +2470,25 @@
                     Trace.endSection();
                     break;
                 case CANCEL_KEYGUARD_EXIT_ANIM:
+                    message = "CANCEL_KEYGUARD_EXIT_ANIM";
                     Trace.beginSection(
                             "KeyguardViewMediator#handleMessage CANCEL_KEYGUARD_EXIT_ANIM");
                     handleCancelKeyguardExitAnimation();
                     Trace.endSection();
                     break;
                 case KEYGUARD_DONE_PENDING_TIMEOUT:
+                    message = "KEYGUARD_DONE_PENDING_TIMEOUT";
                     Trace.beginSection("KeyguardViewMediator#handleMessage"
                             + " KEYGUARD_DONE_PENDING_TIMEOUT");
                     Log.w(TAG, "Timeout while waiting for activity drawn!");
                     Trace.endSection();
                     break;
                 case SYSTEM_READY:
+                    message = "SYSTEM_READY";
                     handleSystemReady();
                     break;
             }
+            Log.d(TAG, "KeyguardViewMediator queue processing message: " + message);
         }
     };
 
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 b796334..ed1bf3e 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
@@ -167,9 +167,10 @@
                                         from = KeyguardState.PRIMARY_BOUNCER,
                                         to = KeyguardState.LOCKSCREEN,
                                         animator =
-                                            getDefaultAnimatorForTransitionsToState(KeyguardState.LOCKSCREEN).apply {
-                                                duration = 0
-                                            }
+                                            getDefaultAnimatorForTransitionsToState(
+                                                    KeyguardState.LOCKSCREEN
+                                                )
+                                                .apply { duration = 0 }
                                     )
                                 )
                             }
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 a8147d0..ff0db34 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
@@ -156,7 +156,12 @@
 
     override fun getDefaultAnimatorForTransitionsToState(toState: KeyguardState): ValueAnimator {
         return ValueAnimator().apply {
-            interpolator = Interpolators.LINEAR
+            interpolator =
+                when (toState) {
+                    KeyguardState.ALTERNATE_BOUNCER -> Interpolators.FAST_OUT_SLOW_IN
+                    else -> Interpolators.LINEAR
+                }
+
             duration =
                 when (toState) {
                     KeyguardState.LOCKSCREEN -> TO_LOCKSCREEN_DURATION
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt
index d1ac49b..f692a39 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt
@@ -49,6 +49,7 @@
 import com.android.systemui.shared.customization.data.content.CustomizationProviderContract as Contract
 import com.android.systemui.statusbar.phone.SystemUIDialog
 import com.android.systemui.statusbar.policy.KeyguardStateController
+import com.android.systemui.util.TraceUtils.Companion.traceAsync
 import dagger.Lazy
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineDispatcher
@@ -442,8 +443,10 @@
     }
 
     private suspend fun isFeatureDisabledByDevicePolicy(): Boolean =
-        withContext(backgroundDispatcher) {
-            devicePolicyManager.areKeyguardShortcutsDisabled(userId = userTracker.userId)
+        traceAsync("isFeatureDisabledByDevicePolicy", TAG) {
+            withContext(backgroundDispatcher) {
+                devicePolicyManager.areKeyguardShortcutsDisabled(userId = userTracker.userId)
+            }
         }
 
     companion object {
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 df7c79f..45bf20d 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
@@ -21,6 +21,7 @@
 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.KeyguardState.ALTERNATE_BOUNCER
 import com.android.systemui.keyguard.shared.model.KeyguardState.AOD
 import com.android.systemui.keyguard.shared.model.KeyguardState.DREAMING
 import com.android.systemui.keyguard.shared.model.KeyguardState.GONE
@@ -51,11 +52,35 @@
 
     /** (any)->GONE transition information */
     val anyStateToGoneTransition: Flow<TransitionStep> =
-        repository.transitions.filter { step -> step.to == KeyguardState.GONE }
+        repository.transitions.filter { step -> step.to == GONE }
 
     /** (any)->AOD transition information */
     val anyStateToAodTransition: Flow<TransitionStep> =
-        repository.transitions.filter { step -> step.to == KeyguardState.AOD }
+        repository.transitions.filter { step -> step.to == AOD }
+
+    /** DREAMING->(any) transition information. */
+    val fromDreamingTransition: Flow<TransitionStep> =
+        repository.transitions.filter { step -> step.from == DREAMING }
+
+    /** (any)->Lockscreen transition information */
+    val anyStateToLockscreenTransition: Flow<TransitionStep> =
+        repository.transitions.filter { step -> step.to == LOCKSCREEN }
+
+    /** (any)->Occluded transition information */
+    val anyStateToOccludedTransition: Flow<TransitionStep> =
+        repository.transitions.filter { step -> step.to == OCCLUDED }
+
+    /** (any)->PrimaryBouncer transition information */
+    val anyStateToPrimaryBouncerTransition: Flow<TransitionStep> =
+        repository.transitions.filter { step -> step.to == PRIMARY_BOUNCER }
+
+    /** (any)->Dreaming transition information */
+    val anyStateToDreamingTransition: Flow<TransitionStep> =
+        repository.transitions.filter { step -> step.to == DREAMING }
+
+    /** (any)->AlternateBouncer transition information */
+    val anyStateToAlternateBouncerTransition: Flow<TransitionStep> =
+        repository.transitions.filter { step -> step.to == ALTERNATE_BOUNCER }
 
     /** AOD->LOCKSCREEN transition information. */
     val aodToLockscreenTransition: Flow<TransitionStep> = repository.transition(AOD, LOCKSCREEN)
@@ -64,6 +89,9 @@
     val dreamingToLockscreenTransition: Flow<TransitionStep> =
         repository.transition(DREAMING, LOCKSCREEN)
 
+    /** GONE->AOD transition information. */
+    val goneToAodTransition: Flow<TransitionStep> = repository.transition(GONE, AOD)
+
     /** GONE->DREAMING transition information. */
     val goneToDreamingTransition: Flow<TransitionStep> = repository.transition(GONE, DREAMING)
 
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 ae6fc9e..0dda625 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
@@ -44,9 +44,9 @@
     abstract fun start()
 
     fun startTransitionTo(
-            toState: KeyguardState,
-            animator: ValueAnimator? = getDefaultAnimatorForTransitionsToState(toState),
-            resetIfCancelled: Boolean = false
+        toState: KeyguardState,
+        animator: ValueAnimator? = getDefaultAnimatorForTransitionsToState(toState),
+        resetIfCancelled: Boolean = false
     ): UUID? {
         if (
             fromState != transitionInteractor.startedKeyguardState.value &&
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/UdfpsKeyguardInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/UdfpsKeyguardInteractor.kt
new file mode 100644
index 0000000..bba0e37
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/UdfpsKeyguardInteractor.kt
@@ -0,0 +1,65 @@
+/*
+ *  Copyright (C) 2023 The Android Open Source Project
+ *
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+
+package com.android.systemui.keyguard.domain.interactor
+
+import android.animation.FloatEvaluator
+import android.animation.IntEvaluator
+import com.android.systemui.common.ui.data.repository.ConfigurationRepository
+import com.android.systemui.dagger.SysUISingleton
+import javax.inject.Inject
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.combine
+
+/** Encapsulates business logic for transitions between UDFPS states on the keyguard. */
+@ExperimentalCoroutinesApi
+@SysUISingleton
+class UdfpsKeyguardInteractor
+@Inject
+constructor(
+    configRepo: ConfigurationRepository,
+    burnInInteractor: BurnInInteractor,
+    keyguardInteractor: KeyguardInteractor,
+) {
+    private val intEvaluator = IntEvaluator()
+    private val floatEvaluator = FloatEvaluator()
+
+    val dozeAmount = keyguardInteractor.dozeAmount
+    val scaleForResolution = configRepo.scaleForResolution
+
+    /** Burn-in offsets for the UDFPS view to mitigate burn-in on AOD. */
+    val burnInOffsets: Flow<BurnInOffsets> =
+        combine(
+            keyguardInteractor.dozeAmount,
+            burnInInteractor.udfpsBurnInXOffset,
+            burnInInteractor.udfpsBurnInYOffset,
+            burnInInteractor.udfpsBurnInProgress
+        ) { dozeAmount, fullyDozingBurnInX, fullyDozingBurnInY, fullyDozingBurnInProgress ->
+            BurnInOffsets(
+                intEvaluator.evaluate(dozeAmount, 0, fullyDozingBurnInX),
+                intEvaluator.evaluate(dozeAmount, 0, fullyDozingBurnInY),
+                floatEvaluator.evaluate(dozeAmount, 0, fullyDozingBurnInProgress),
+            )
+        }
+}
+
+data class BurnInOffsets(
+    val burnInXOffset: Int, // current x burn in offset based on the aodTransitionAmount
+    val burnInYOffset: Int, // current y burn in offset based on the aodTransitionAmount
+    val burnInProgress: Float, // current progress based on the aodTransitionAmount
+)
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/adapter/UdfpsKeyguardViewControllerAdapter.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/adapter/UdfpsKeyguardViewControllerAdapter.kt
new file mode 100644
index 0000000..ebf1beb
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/adapter/UdfpsKeyguardViewControllerAdapter.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.keyguard.ui.adapter
+
+/**
+ * Temporary adapter class while
+ * [com.android.systemui.biometrics.ui.controller.UdfpsKeyguardViewController] is being refactored
+ * before [com.android.systemui.biometrics.UdfpsKeyguardViewControllerLegacy] is removed.
+ *
+ * TODO (b/278719514): Delete once udfps keyguard view is fully refactored.
+ */
+interface UdfpsKeyguardViewControllerAdapter
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/UdfpsAodFingerprintViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/UdfpsAodFingerprintViewBinder.kt
new file mode 100644
index 0000000..728dd39
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/UdfpsAodFingerprintViewBinder.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.keyguard.ui.binder
+
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.repeatOnLifecycle
+import com.airbnb.lottie.LottieAnimationView
+import com.android.systemui.keyguard.ui.viewmodel.UdfpsAodViewModel
+import com.android.systemui.lifecycle.repeatWhenAttached
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.launch
+
+@ExperimentalCoroutinesApi
+object UdfpsAodFingerprintViewBinder {
+
+    /**
+     * Drives UI for the UDFPS aod fingerprint view. See [UdfpsFingerprintViewBinder] and
+     * [UdfpsBackgroundViewBinder].
+     */
+    @JvmStatic
+    fun bind(
+        view: LottieAnimationView,
+        viewModel: UdfpsAodViewModel,
+    ) {
+        view.repeatWhenAttached {
+            repeatOnLifecycle(Lifecycle.State.STARTED) {
+                launch {
+                    viewModel.burnInOffsets.collect { burnInOffsets ->
+                        view.progress = burnInOffsets.burnInProgress
+                        view.translationX = burnInOffsets.burnInXOffset.toFloat()
+                        view.translationY = burnInOffsets.burnInYOffset.toFloat()
+                    }
+                }
+
+                launch { viewModel.alpha.collect { alpha -> view.alpha = alpha } }
+
+                launch {
+                    viewModel.padding.collect { padding ->
+                        view.setPadding(padding, padding, padding, padding)
+                    }
+                }
+            }
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/UdfpsBackgroundViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/UdfpsBackgroundViewBinder.kt
new file mode 100644
index 0000000..26ef468
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/UdfpsBackgroundViewBinder.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.keyguard.ui.binder
+
+import android.content.res.ColorStateList
+import android.widget.ImageView
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.repeatOnLifecycle
+import com.android.systemui.keyguard.ui.viewmodel.BackgroundViewModel
+import com.android.systemui.lifecycle.repeatWhenAttached
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.launch
+
+@ExperimentalCoroutinesApi
+object UdfpsBackgroundViewBinder {
+
+    /**
+     * Drives UI for the udfps background view. See [UdfpsAodFingerprintViewBinder] and
+     * [UdfpsFingerprintViewBinder].
+     */
+    @JvmStatic
+    fun bind(
+        view: ImageView,
+        viewModel: BackgroundViewModel,
+    ) {
+        view.repeatWhenAttached {
+            repeatOnLifecycle(Lifecycle.State.STARTED) {
+                launch {
+                    viewModel.transition.collect {
+                        view.alpha = it.alpha
+                        view.scaleX = it.scale
+                        view.scaleY = it.scale
+                        view.imageTintList = ColorStateList.valueOf(it.color)
+                    }
+                }
+            }
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/UdfpsFingerprintViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/UdfpsFingerprintViewBinder.kt
new file mode 100644
index 0000000..0ab8e52
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/UdfpsFingerprintViewBinder.kt
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package com.android.systemui.keyguard.ui.binder
+
+import android.graphics.PorterDuff
+import android.graphics.PorterDuffColorFilter
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.repeatOnLifecycle
+import com.airbnb.lottie.LottieAnimationView
+import com.airbnb.lottie.LottieProperty
+import com.airbnb.lottie.model.KeyPath
+import com.android.systemui.keyguard.ui.viewmodel.FingerprintViewModel
+import com.android.systemui.lifecycle.repeatWhenAttached
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.launch
+
+@ExperimentalCoroutinesApi
+object UdfpsFingerprintViewBinder {
+    private var udfpsIconColor = 0
+
+    /**
+     * Drives UI for the UDFPS fingerprint view when it's NOT on aod. See
+     * [UdfpsAodFingerprintViewBinder] and [UdfpsBackgroundViewBinder].
+     */
+    @JvmStatic
+    fun bind(
+        view: LottieAnimationView,
+        viewModel: FingerprintViewModel,
+    ) {
+        view.repeatWhenAttached {
+            repeatOnLifecycle(Lifecycle.State.STARTED) {
+                launch {
+                    viewModel.transition.collect {
+                        view.alpha = it.alpha
+                        view.scaleX = it.scale
+                        view.scaleY = it.scale
+                        if (udfpsIconColor != (it.color)) {
+                            udfpsIconColor = it.color
+                            view.invalidate()
+                        }
+                    }
+                }
+
+                launch {
+                    viewModel.burnInOffsets.collect { burnInOffsets ->
+                        view.translationX = burnInOffsets.burnInXOffset.toFloat()
+                        view.translationY = burnInOffsets.burnInYOffset.toFloat()
+                    }
+                }
+
+                launch {
+                    viewModel.dozeAmount.collect { dozeAmount ->
+                        // Lottie progress represents: aod=0 to lockscreen=1
+                        view.progress = 1f - dozeAmount
+                    }
+                }
+
+                launch {
+                    viewModel.padding.collect { padding ->
+                        view.setPadding(padding, padding, padding, padding)
+                    }
+                }
+            }
+        }
+
+        // Add a callback that updates the color to `udfpsIconColor` whenever invalidate is called
+        view.addValueCallback(KeyPath("**"), LottieProperty.COLOR_FILTER) {
+            PorterDuffColorFilter(udfpsIconColor, PorterDuff.Mode.SRC_ATOP)
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/UdfpsKeyguardInternalViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/UdfpsKeyguardInternalViewBinder.kt
new file mode 100644
index 0000000..b568a9a
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/UdfpsKeyguardInternalViewBinder.kt
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package com.android.systemui.biometrics.ui.binder
+
+import android.view.View
+import com.android.systemui.R
+import com.android.systemui.keyguard.ui.binder.UdfpsAodFingerprintViewBinder
+import com.android.systemui.keyguard.ui.binder.UdfpsBackgroundViewBinder
+import com.android.systemui.keyguard.ui.binder.UdfpsFingerprintViewBinder
+import com.android.systemui.keyguard.ui.viewmodel.BackgroundViewModel
+import com.android.systemui.keyguard.ui.viewmodel.FingerprintViewModel
+import com.android.systemui.keyguard.ui.viewmodel.UdfpsAodViewModel
+import com.android.systemui.keyguard.ui.viewmodel.UdfpsKeyguardInternalViewModel
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+
+@ExperimentalCoroutinesApi
+object UdfpsKeyguardInternalViewBinder {
+
+    @JvmStatic
+    fun bind(
+        view: View,
+        viewModel: UdfpsKeyguardInternalViewModel,
+        aodViewModel: UdfpsAodViewModel,
+        fingerprintViewModel: FingerprintViewModel,
+        backgroundViewModel: BackgroundViewModel,
+    ) {
+        view.accessibilityDelegate = viewModel.accessibilityDelegate
+
+        // bind child views
+        UdfpsAodFingerprintViewBinder.bind(view.findViewById(R.id.udfps_aod_fp), aodViewModel)
+        UdfpsFingerprintViewBinder.bind(
+            view.findViewById(R.id.udfps_lockscreen_fp),
+            fingerprintViewModel
+        )
+        UdfpsBackgroundViewBinder.bind(
+            view.findViewById(R.id.udfps_keyguard_fp_bg),
+            backgroundViewModel
+        )
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/UdfpsKeyguardViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/UdfpsKeyguardViewBinder.kt
new file mode 100644
index 0000000..667abae
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/UdfpsKeyguardViewBinder.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.keyguard.ui.binder
+
+import android.graphics.RectF
+import android.view.View
+import android.widget.FrameLayout
+import androidx.asynclayoutinflater.view.AsyncLayoutInflater
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.repeatOnLifecycle
+import com.android.systemui.R
+import com.android.systemui.biometrics.UdfpsKeyguardView
+import com.android.systemui.biometrics.ui.binder.UdfpsKeyguardInternalViewBinder
+import com.android.systemui.keyguard.ui.viewmodel.BackgroundViewModel
+import com.android.systemui.keyguard.ui.viewmodel.FingerprintViewModel
+import com.android.systemui.keyguard.ui.viewmodel.UdfpsAodViewModel
+import com.android.systemui.keyguard.ui.viewmodel.UdfpsKeyguardInternalViewModel
+import com.android.systemui.keyguard.ui.viewmodel.UdfpsKeyguardViewModel
+import com.android.systemui.lifecycle.repeatWhenAttached
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.launch
+
+@ExperimentalCoroutinesApi
+object UdfpsKeyguardViewBinder {
+    /**
+     * Drives UI for the keyguard UDFPS view. Inflates child views on a background thread. For view
+     * binders for its child views, see [UdfpsFingerprintViewBinder], [UdfpsBackgroundViewBinder] &
+     * [UdfpsAodFingerprintViewBinder].
+     */
+    @JvmStatic
+    fun bind(
+        view: UdfpsKeyguardView,
+        viewModel: UdfpsKeyguardViewModel,
+        udfpsKeyguardInternalViewModel: UdfpsKeyguardInternalViewModel,
+        aodViewModel: UdfpsAodViewModel,
+        fingerprintViewModel: FingerprintViewModel,
+        backgroundViewModel: BackgroundViewModel,
+    ) {
+        view.useExpandedOverlay(viewModel.useExpandedOverlay())
+
+        val layoutInflaterFinishListener =
+            AsyncLayoutInflater.OnInflateFinishedListener { inflatedInternalView, _, parent ->
+                UdfpsKeyguardInternalViewBinder.bind(
+                    inflatedInternalView,
+                    udfpsKeyguardInternalViewModel,
+                    aodViewModel,
+                    fingerprintViewModel,
+                    backgroundViewModel,
+                )
+                if (viewModel.useExpandedOverlay()) {
+                    val lp = inflatedInternalView.layoutParams as FrameLayout.LayoutParams
+                    lp.width = viewModel.sensorBounds.width()
+                    lp.height = viewModel.sensorBounds.height()
+                    val relativeToView =
+                        getBoundsRelativeToView(
+                            inflatedInternalView,
+                            RectF(viewModel.sensorBounds),
+                        )
+                    lp.setMarginsRelative(
+                        relativeToView.left.toInt(),
+                        relativeToView.top.toInt(),
+                        relativeToView.right.toInt(),
+                        relativeToView.bottom.toInt(),
+                    )
+                    parent!!.addView(inflatedInternalView, lp)
+                } else {
+                    parent!!.addView(inflatedInternalView)
+                }
+            }
+        val inflater = AsyncLayoutInflater(view.context)
+        inflater.inflate(R.layout.udfps_keyguard_view_internal, view, layoutInflaterFinishListener)
+
+        view.repeatWhenAttached {
+            repeatOnLifecycle(Lifecycle.State.CREATED) {
+                launch {
+                    combine(aodViewModel.isVisible, fingerprintViewModel.visible) {
+                            isAodVisible,
+                            isFingerprintVisible ->
+                            isAodVisible || isFingerprintVisible
+                        }
+                        .collect { view.setVisible(it) }
+                }
+            }
+        }
+    }
+
+    /**
+     * Converts coordinates of RectF relative to the screen to coordinates relative to this view.
+     *
+     * @param bounds RectF based off screen coordinates in current orientation
+     */
+    private fun getBoundsRelativeToView(view: View, bounds: RectF): RectF {
+        val pos: IntArray = view.locationOnScreen
+        return RectF(
+            bounds.left - pos[0],
+            bounds.top - pos[1],
+            bounds.right - pos[0],
+            bounds.bottom - pos[1]
+        )
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/KeyguardRootView.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/KeyguardRootView.kt
index a62f383..0077f2d 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/KeyguardRootView.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/KeyguardRootView.kt
@@ -19,10 +19,7 @@
 
 import android.content.Context
 import android.util.AttributeSet
-import android.view.Gravity
-import android.view.ViewGroup.LayoutParams.MATCH_PARENT
-import android.view.ViewGroup.LayoutParams.WRAP_CONTENT
-import android.widget.FrameLayout
+import androidx.constraintlayout.widget.ConstraintLayout
 import com.android.keyguard.LockIconView
 import com.android.systemui.R
 
@@ -31,7 +28,7 @@
     context: Context,
     private val attrs: AttributeSet?,
 ) :
-    FrameLayout(
+    ConstraintLayout(
         context,
         attrs,
     ) {
@@ -43,31 +40,11 @@
 
     private fun addIndicationTextArea() {
         val view = KeyguardIndicationArea(context, attrs)
-        addView(
-            view,
-            FrameLayout.LayoutParams(
-                    MATCH_PARENT,
-                    WRAP_CONTENT,
-                )
-                .apply {
-                    gravity = Gravity.BOTTOM or Gravity.CENTER_HORIZONTAL
-                    bottomMargin = R.dimen.keyguard_indication_margin_bottom.dp()
-                }
-        )
+        addView(view)
     }
 
     private fun addLockIconView() {
         val view = LockIconView(context, attrs).apply { id = R.id.lock_icon_view }
-        addView(
-            view,
-            LayoutParams(
-                WRAP_CONTENT,
-                WRAP_CONTENT,
-            )
-        )
-    }
-
-    private fun Int.dp(): Int {
-        return context.resources.getDimensionPixelSize(this)
+        addView(view)
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/DefaultLockscreenLayout.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/DefaultLockscreenLayout.kt
new file mode 100644
index 0000000..baaeb60
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/DefaultLockscreenLayout.kt
@@ -0,0 +1,152 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package com.android.systemui.keyguard.ui.view.layout
+
+import android.content.Context
+import android.graphics.Point
+import android.graphics.Rect
+import android.util.DisplayMetrics
+import android.view.View
+import android.view.ViewGroup.LayoutParams.MATCH_PARENT
+import android.view.ViewGroup.LayoutParams.WRAP_CONTENT
+import android.view.WindowManager
+import androidx.annotation.VisibleForTesting
+import androidx.constraintlayout.widget.ConstraintLayout
+import androidx.constraintlayout.widget.ConstraintSet
+import androidx.constraintlayout.widget.ConstraintSet.BOTTOM
+import androidx.constraintlayout.widget.ConstraintSet.END
+import androidx.constraintlayout.widget.ConstraintSet.PARENT_ID
+import androidx.constraintlayout.widget.ConstraintSet.START
+import androidx.constraintlayout.widget.ConstraintSet.TOP
+import com.android.keyguard.KeyguardUpdateMonitor
+import com.android.systemui.R
+import com.android.systemui.biometrics.AuthController
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.keyguard.ui.view.KeyguardRootView
+import javax.inject.Inject
+
+/**
+ * Positions elements of the lockscreen to the default position.
+ *
+ * This will be the most common use case for phones in portrait mode.
+ */
+@SysUISingleton
+class DefaultLockscreenLayout
+@Inject
+constructor(
+    private val authController: AuthController,
+    private val keyguardUpdateMonitor: KeyguardUpdateMonitor,
+    private val windowManager: WindowManager,
+    private val context: Context,
+) : LockscreenLayout {
+    override val id: String = DEFAULT
+
+    override fun layoutIndicationArea(rootView: KeyguardRootView) {
+        val indicationArea = rootView.findViewById<View>(R.id.keyguard_indication_area) ?: return
+
+        rootView.getConstraintSet().apply {
+            constrainWidth(indicationArea.id, MATCH_PARENT)
+            constrainHeight(indicationArea.id, WRAP_CONTENT)
+            connect(
+                indicationArea.id,
+                BOTTOM,
+                PARENT_ID,
+                BOTTOM,
+                R.dimen.keyguard_indication_margin_bottom.dp()
+            )
+            connect(indicationArea.id, START, PARENT_ID, START)
+            connect(indicationArea.id, END, PARENT_ID, END)
+            applyTo(rootView)
+        }
+    }
+
+    override fun layoutLockIcon(rootView: KeyguardRootView) {
+        val isUdfpsSupported = keyguardUpdateMonitor.isUdfpsSupported
+        val scaleFactor: Float = authController.scaleFactor
+        val mBottomPaddingPx = R.dimen.lock_icon_margin_bottom.dp()
+        val mDefaultPaddingPx = R.dimen.lock_icon_padding.dp()
+        val scaledPadding: Int = (mDefaultPaddingPx * scaleFactor).toInt()
+        val bounds = windowManager.currentWindowMetrics.bounds
+        val widthPixels = bounds.right.toFloat()
+        val heightPixels = bounds.bottom.toFloat()
+        val defaultDensity =
+            DisplayMetrics.DENSITY_DEVICE_STABLE.toFloat() /
+                DisplayMetrics.DENSITY_DEFAULT.toFloat()
+        val lockIconRadiusPx = (defaultDensity * 36).toInt()
+
+        if (isUdfpsSupported) {
+            authController.udfpsLocation?.let { udfpsLocation ->
+                centerLockIcon(udfpsLocation, authController.udfpsRadius, scaledPadding, rootView)
+            }
+        } else {
+            centerLockIcon(
+                Point(
+                    (widthPixels / 2).toInt(),
+                    (heightPixels - ((mBottomPaddingPx + lockIconRadiusPx) * scaleFactor)).toInt()
+                ),
+                lockIconRadiusPx * scaleFactor,
+                scaledPadding,
+                rootView
+            )
+        }
+    }
+
+    @VisibleForTesting
+    internal fun centerLockIcon(
+        center: Point,
+        radius: Float,
+        drawablePadding: Int,
+        rootView: KeyguardRootView,
+    ) {
+        val lockIconView = rootView.findViewById<View>(R.id.lock_icon_view) ?: return
+        val lockIcon = lockIconView.findViewById<View>(R.id.lock_icon) ?: return
+        lockIcon.setPadding(drawablePadding, drawablePadding, drawablePadding, drawablePadding)
+
+        val sensorRect =
+            Rect().apply {
+                set(
+                    center.x - radius.toInt(),
+                    center.y - radius.toInt(),
+                    center.x + radius.toInt(),
+                    center.y + radius.toInt(),
+                )
+            }
+
+        rootView.getConstraintSet().apply {
+            constrainWidth(lockIconView.id, sensorRect.right - sensorRect.left)
+            constrainHeight(lockIconView.id, sensorRect.bottom - sensorRect.top)
+            connect(lockIconView.id, TOP, PARENT_ID, TOP, sensorRect.top)
+            connect(lockIconView.id, START, PARENT_ID, START, sensorRect.left)
+            applyTo(rootView)
+        }
+    }
+
+    private fun Int.dp(): Int {
+        return context.resources.getDimensionPixelSize(this)
+    }
+
+    private fun ConstraintLayout.getConstraintSet(): ConstraintSet {
+        val cs = ConstraintSet()
+        cs.clone(this)
+        return cs
+    }
+
+    companion object {
+        const val DEFAULT = "default"
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/KeyguardLayoutManager.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/KeyguardLayoutManager.kt
new file mode 100644
index 0000000..9bc6302
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/KeyguardLayoutManager.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.keyguard.ui.view.layout
+
+import android.content.res.Configuration
+import androidx.constraintlayout.widget.ConstraintSet
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.keyguard.ui.view.KeyguardRootView
+import com.android.systemui.keyguard.ui.view.layout.DefaultLockscreenLayout.Companion.DEFAULT
+import com.android.systemui.statusbar.policy.ConfigurationController
+import javax.inject.Inject
+
+/**
+ * Manages layout changes for the lockscreen.
+ *
+ * To add a layout, add an entry to the map with a unique id and call #transitionToLayout(string).
+ */
+@SysUISingleton
+class KeyguardLayoutManager
+@Inject
+constructor(
+    configurationController: ConfigurationController,
+    layouts: Set<@JvmSuppressWildcards LockscreenLayout>,
+    private val keyguardRootView: KeyguardRootView,
+) {
+    internal val layoutIdMap: Map<String, LockscreenLayout> = layouts.associateBy { it.id }
+    private var layout: LockscreenLayout? = layoutIdMap[DEFAULT]
+
+    init {
+        configurationController.addCallback(
+            object : ConfigurationController.ConfigurationListener {
+                override fun onConfigChanged(newConfig: Configuration?) {
+                    layoutViews()
+                }
+            }
+        )
+    }
+
+    /**
+     * Transitions to a layout.
+     *
+     * @param layoutId
+     * @return whether the transition has succeeded.
+     */
+    fun transitionToLayout(layoutId: String): Boolean {
+        layout = layoutIdMap[layoutId] ?: return false
+        layoutViews()
+        return true
+    }
+
+    fun layoutViews() {
+        layout?.layoutViews(keyguardRootView)
+    }
+
+    companion object {
+        const val TAG = "KeyguardLayoutManager"
+    }
+}
+
+interface LockscreenLayout {
+    val id: String
+
+    fun layoutViews(rootView: KeyguardRootView) {
+        // Clear constraints.
+        ConstraintSet()
+            .apply {
+                clone(rootView)
+                knownIds.forEach { getConstraint(it).layout.copyFrom(ConstraintSet.Layout()) }
+            }
+            .applyTo(rootView)
+        layoutIndicationArea(rootView)
+        layoutLockIcon(rootView)
+    }
+    fun layoutIndicationArea(rootView: KeyguardRootView)
+    fun layoutLockIcon(rootView: KeyguardRootView)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/KeyguardLayoutManagerCommandListener.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/KeyguardLayoutManagerCommandListener.kt
new file mode 100644
index 0000000..b351ea8
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/KeyguardLayoutManagerCommandListener.kt
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.ui.view.layout
+
+import com.android.systemui.statusbar.commandline.Command
+import com.android.systemui.statusbar.commandline.CommandRegistry
+import java.io.PrintWriter
+import javax.inject.Inject
+
+/** Uses $ adb shell cmd statusbar layout <LayoutId> */
+class KeyguardLayoutManagerCommandListener
+@Inject
+constructor(
+    private val commandRegistry: CommandRegistry,
+    private val keyguardLayoutManager: KeyguardLayoutManager
+) {
+    private val layoutCommand = KeyguardLayoutManagerCommand()
+
+    fun start() {
+        commandRegistry.registerCommand(COMMAND) { layoutCommand }
+    }
+
+    internal inner class KeyguardLayoutManagerCommand : Command {
+        override fun execute(pw: PrintWriter, args: List<String>) {
+            val arg = args.getOrNull(0)
+            if (arg == null || arg.lowercase() == "help") {
+                help(pw)
+                return
+            }
+
+            if (keyguardLayoutManager.transitionToLayout(arg)) {
+                pw.println("Transition succeeded!")
+            } else {
+                pw.println("Invalid argument! To see available layout ids, run:")
+                pw.println("$ adb shell cmd statusbar layout help")
+            }
+        }
+
+        override fun help(pw: PrintWriter) {
+            pw.println("Usage: $ adb shell cmd statusbar layout <layoutId>")
+            pw.println("Existing Layout Ids: ")
+            keyguardLayoutManager.layoutIdMap.forEach { entry -> pw.println("${entry.key}") }
+        }
+    }
+
+    companion object {
+        internal const val COMMAND = "layout"
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/LockscreenLayoutModule.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/LockscreenLayoutModule.kt
new file mode 100644
index 0000000..00f93e3
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/LockscreenLayoutModule.kt
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package com.android.systemui.keyguard.ui.view.layout
+
+import dagger.Binds
+import dagger.Module
+import dagger.multibindings.IntoSet
+
+@Module
+abstract class LockscreenLayoutModule {
+    @Binds
+    @IntoSet
+    abstract fun bindDefaultLayout(
+        defaultLockscreenLayout: DefaultLockscreenLayout
+    ): LockscreenLayout
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModel.kt
index 9ca4bd6..e24d326 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModel.kt
@@ -48,7 +48,7 @@
         )
 
     val transitionEnded =
-        keyguardTransitionInteractor.dreamingToLockscreenTransition.filter { step ->
+        keyguardTransitionInteractor.fromDreamingTransition.filter { step ->
             step.transitionState == TransitionState.FINISHED ||
                 step.transitionState == TransitionState.CANCELED
         }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsAodViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsAodViewModel.kt
new file mode 100644
index 0000000..667c2f1
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsAodViewModel.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.keyguard.ui.viewmodel
+
+import android.content.Context
+import com.android.systemui.R
+import com.android.systemui.keyguard.domain.interactor.BurnInOffsets
+import com.android.systemui.keyguard.domain.interactor.UdfpsKeyguardInteractor
+import javax.inject.Inject
+import kotlin.math.roundToInt
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.map
+
+/** View-model for UDFPS AOD view. */
+@ExperimentalCoroutinesApi
+class UdfpsAodViewModel
+@Inject
+constructor(
+    val interactor: UdfpsKeyguardInteractor,
+    val context: Context,
+) {
+    val alpha: Flow<Float> = interactor.dozeAmount
+    val burnInOffsets: Flow<BurnInOffsets> = interactor.burnInOffsets
+    val isVisible: Flow<Boolean> = alpha.map { it != 0f }
+
+    // Padding between the fingerprint icon and its bounding box in pixels.
+    val padding: Flow<Int> =
+        interactor.scaleForResolution.map { scale ->
+            (context.resources.getDimensionPixelSize(R.dimen.lock_icon_padding) * scale)
+                .roundToInt()
+        }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsKeyguardInternalViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsKeyguardInternalViewModel.kt
new file mode 100644
index 0000000..d894a11
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsKeyguardInternalViewModel.kt
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.ui.viewmodel
+
+import com.android.systemui.biometrics.UdfpsKeyguardAccessibilityDelegate
+import javax.inject.Inject
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+
+@ExperimentalCoroutinesApi
+class UdfpsKeyguardInternalViewModel
+@Inject
+constructor(val accessibilityDelegate: UdfpsKeyguardAccessibilityDelegate)
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsKeyguardViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsKeyguardViewModel.kt
new file mode 100644
index 0000000..929f27f
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsKeyguardViewModel.kt
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.ui.viewmodel
+
+import android.graphics.Rect
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags
+import javax.inject.Inject
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+
+@ExperimentalCoroutinesApi
+class UdfpsKeyguardViewModel
+@Inject
+constructor(
+    private val featureFlags: FeatureFlags,
+) {
+    var sensorBounds: Rect = Rect()
+
+    fun useExpandedOverlay(): Boolean {
+        return featureFlags.isEnabled(Flags.UDFPS_NEW_TOUCH_DETECTION)
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsKeyguardViewModels.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsKeyguardViewModels.kt
new file mode 100644
index 0000000..098b481
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsKeyguardViewModels.kt
@@ -0,0 +1,36 @@
+package com.android.systemui.keyguard.ui.viewmodel
+
+import android.graphics.Rect
+import com.android.systemui.biometrics.UdfpsKeyguardView
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.keyguard.ui.binder.UdfpsKeyguardViewBinder
+import javax.inject.Inject
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+
+@ExperimentalCoroutinesApi
+@SysUISingleton
+class UdfpsKeyguardViewModels
+@Inject
+constructor(
+    private val viewModel: UdfpsKeyguardViewModel,
+    private val internalViewModel: UdfpsKeyguardInternalViewModel,
+    private val aodViewModel: UdfpsAodViewModel,
+    private val lockscreenFingerprintViewModel: FingerprintViewModel,
+    private val lockscreenBackgroundViewModel: BackgroundViewModel,
+) {
+
+    fun setSensorBounds(sensorBounds: Rect) {
+        viewModel.sensorBounds = sensorBounds
+    }
+
+    fun bindViews(view: UdfpsKeyguardView) {
+        UdfpsKeyguardViewBinder.bind(
+            view,
+            viewModel,
+            internalViewModel,
+            aodViewModel,
+            lockscreenFingerprintViewModel,
+            lockscreenBackgroundViewModel
+        )
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsLockscreenViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsLockscreenViewModel.kt
new file mode 100644
index 0000000..fd4b666
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsLockscreenViewModel.kt
@@ -0,0 +1,162 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.ui.viewmodel
+
+import android.content.Context
+import androidx.annotation.ColorInt
+import com.android.settingslib.Utils.getColorAttrDefaultColor
+import com.android.systemui.R
+import com.android.systemui.keyguard.domain.interactor.BurnInOffsets
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
+import com.android.systemui.keyguard.domain.interactor.UdfpsKeyguardInteractor
+import com.android.systemui.keyguard.shared.model.KeyguardState
+import javax.inject.Inject
+import kotlin.math.roundToInt
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.merge
+
+/** View-model for UDFPS lockscreen views. */
+@ExperimentalCoroutinesApi
+open class UdfpsLockscreenViewModel(
+    context: Context,
+    lockscreenColorResId: Int,
+    alternateBouncerColorResId: Int,
+    transitionInteractor: KeyguardTransitionInteractor,
+) {
+    private val toLockscreen: Flow<TransitionViewModel> =
+        transitionInteractor.anyStateToLockscreenTransition.map {
+            TransitionViewModel(
+                alpha =
+                    if (it.from == KeyguardState.AOD) {
+                        it.value // animate
+                    } else {
+                        1f
+                    },
+                scale = 1f,
+                color = getColorAttrDefaultColor(context, lockscreenColorResId),
+            )
+        }
+
+    private val toAlternateBouncer: Flow<TransitionViewModel> =
+        transitionInteractor.anyStateToAlternateBouncerTransition.map {
+            TransitionViewModel(
+                alpha = 1f,
+                scale =
+                    if (visibleInKeyguardState(it.from)) {
+                        1f
+                    } else {
+                        it.value
+                    },
+                color = getColorAttrDefaultColor(context, alternateBouncerColorResId),
+            )
+        }
+
+    private val fadeOut: Flow<TransitionViewModel> =
+        merge(
+                transitionInteractor.anyStateToGoneTransition,
+                transitionInteractor.anyStateToAodTransition,
+                transitionInteractor.anyStateToOccludedTransition,
+                transitionInteractor.anyStateToPrimaryBouncerTransition,
+                transitionInteractor.anyStateToDreamingTransition,
+            )
+            .map {
+                TransitionViewModel(
+                    alpha =
+                        if (visibleInKeyguardState(it.from)) {
+                            1f - it.value
+                        } else {
+                            0f
+                        },
+                    scale = 1f,
+                    color =
+                        if (it.from == KeyguardState.ALTERNATE_BOUNCER) {
+                            getColorAttrDefaultColor(context, alternateBouncerColorResId)
+                        } else {
+                            getColorAttrDefaultColor(context, lockscreenColorResId)
+                        },
+                )
+            }
+
+    private fun visibleInKeyguardState(state: KeyguardState): Boolean {
+        return when (state) {
+            KeyguardState.OFF,
+            KeyguardState.DOZING,
+            KeyguardState.DREAMING,
+            KeyguardState.AOD,
+            KeyguardState.PRIMARY_BOUNCER,
+            KeyguardState.GONE,
+            KeyguardState.OCCLUDED -> false
+            KeyguardState.LOCKSCREEN,
+            KeyguardState.ALTERNATE_BOUNCER -> true
+        }
+    }
+
+    val transition: Flow<TransitionViewModel> =
+        merge(
+            toAlternateBouncer,
+            toLockscreen,
+            fadeOut,
+        )
+    val visible: Flow<Boolean> = transition.map { it.alpha != 0f }
+}
+
+@ExperimentalCoroutinesApi
+class FingerprintViewModel
+@Inject
+constructor(
+    val context: Context,
+    transitionInteractor: KeyguardTransitionInteractor,
+    interactor: UdfpsKeyguardInteractor,
+) :
+    UdfpsLockscreenViewModel(
+        context,
+        android.R.attr.textColorPrimary,
+        com.android.internal.R.attr.materialColorOnPrimaryFixed,
+        transitionInteractor,
+    ) {
+    val dozeAmount: Flow<Float> = interactor.dozeAmount
+    val burnInOffsets: Flow<BurnInOffsets> = interactor.burnInOffsets
+
+    // Padding between the fingerprint icon and its bounding box in pixels.
+    val padding: Flow<Int> =
+        interactor.scaleForResolution.map { scale ->
+            (context.resources.getDimensionPixelSize(R.dimen.lock_icon_padding) * scale)
+                .roundToInt()
+        }
+}
+
+@ExperimentalCoroutinesApi
+class BackgroundViewModel
+@Inject
+constructor(
+    val context: Context,
+    transitionInteractor: KeyguardTransitionInteractor,
+) :
+    UdfpsLockscreenViewModel(
+        context,
+        com.android.internal.R.attr.colorSurface,
+        com.android.internal.R.attr.materialColorPrimaryFixed,
+        transitionInteractor,
+    )
+
+data class TransitionViewModel(
+    val alpha: Float,
+    val scale: Float,
+    @ColorInt val color: Int,
+)
diff --git a/packages/SystemUI/src/com/android/systemui/log/ScreenDecorationsLogger.kt b/packages/SystemUI/src/com/android/systemui/log/ScreenDecorationsLogger.kt
index 150de26..702a23e 100644
--- a/packages/SystemUI/src/com/android/systemui/log/ScreenDecorationsLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/log/ScreenDecorationsLogger.kt
@@ -189,15 +189,15 @@
         )
     }
 
-    fun logDisplayModeChanged(currentMode: Int, newMode: Int) {
+    fun logDisplaySizeChanged(currentSize: Point, newSize: Point) {
         logBuffer.log(
             TAG,
             INFO,
             {
-                int1 = currentMode
-                int2 = newMode
+                str1 = currentSize.flattenToString()
+                str2 = newSize.flattenToString()
             },
-            { "Resolution changed, deferring mode change to $int2, staying at $int1" },
+            { "Resolution changed, deferring size change to $str2, staying at $str1" },
         )
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataManager.kt b/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataManager.kt
index 6b993ce..a2bb950 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataManager.kt
@@ -1253,6 +1253,9 @@
         return try {
             val options = BroadcastOptions.makeBasic()
             options.setInteractive(true)
+            options.setPendingIntentBackgroundActivityStartMode(
+                BroadcastOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED
+            )
             intent.send(options.toBundle())
             true
         } catch (e: PendingIntent.CanceledException) {
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/KeyguardMediaController.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/KeyguardMediaController.kt
index 30ee147..2883210 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/KeyguardMediaController.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/KeyguardMediaController.kt
@@ -128,6 +128,15 @@
 
     var visibilityChangedListener: ((Boolean) -> Unit)? = null
 
+    /**
+     * Whether the doze wake up animation is delayed and we are currently waiting for it to start.
+     */
+    var isDozeWakeUpAnimationWaiting: Boolean = false
+        set(value) {
+            field = value
+            refreshMediaPosition()
+        }
+
     /** single pane media container placed at the top of the notifications list */
     var singlePaneContainer: MediaContainerView? = null
         private set
@@ -221,7 +230,13 @@
         // by the clock. This is not the case for single-line clock though.
         // For single shade, we don't need to do it, because media is a child of NSSL, which already
         // gets hidden on AOD.
-        return !statusBarStateController.isDozing
+        // Media also has to be hidden when waking up from dozing, and the doze wake up animation is
+        // delayed and waiting to be started.
+        // This is to stay in sync with the delaying of the horizontal alignment of the rest of the
+        // keyguard container, that is also delayed until the "wait" is over.
+        // If we show media during this waiting period, the shade will still be centered, and using
+        // the entire width of the screen, and making media show fully stretched.
+        return !statusBarStateController.isDozing && !isDozeWakeUpAnimationWaiting
     }
 
     private fun showMediaPlayer() {
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaControlPanel.java b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaControlPanel.java
index 35082fd..a978b92 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaControlPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaControlPanel.java
@@ -23,6 +23,7 @@
 import android.animation.Animator;
 import android.animation.AnimatorInflater;
 import android.animation.AnimatorSet;
+import android.app.ActivityOptions;
 import android.app.BroadcastOptions;
 import android.app.PendingIntent;
 import android.app.WallpaperColors;
@@ -535,7 +536,10 @@
                         mLockscreenUserManager.getCurrentUserId());
                 if (showOverLockscreen) {
                     try {
-                        clickIntent.send();
+                        ActivityOptions opts = ActivityOptions.makeBasic();
+                        opts.setPendingIntentBackgroundActivityStartMode(
+                                ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED);
+                        clickIntent.send(opts.toBundle());
                     } catch (PendingIntent.CanceledException e) {
                         Log.e(TAG, "Pending intent for " + key + " was cancelled");
                     }
@@ -684,6 +688,8 @@
                                 try {
                                     BroadcastOptions options = BroadcastOptions.makeBasic();
                                     options.setInteractive(true);
+                                    options.setPendingIntentBackgroundActivityStartMode(
+                                            ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED);
                                     deviceIntent.send(options.toBundle());
                                 } catch (PendingIntent.CanceledException e) {
                                     Log.e(TAG, "Device pending intent was canceled");
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapter.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapter.java
index 318cd99..26a7d04 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapter.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapter.java
@@ -20,6 +20,7 @@
 import static com.android.settingslib.media.MediaDevice.SelectionBehavior.SELECTION_BEHAVIOR_NONE;
 import static com.android.settingslib.media.MediaDevice.SelectionBehavior.SELECTION_BEHAVIOR_TRANSFER;
 
+import android.annotation.DrawableRes;
 import android.content.Context;
 import android.content.res.ColorStateList;
 import android.graphics.drawable.AnimatedVectorDrawable;
@@ -181,27 +182,23 @@
                                     mController.getSelectedMediaDevice(), device)));
                     boolean isHost = device.isHostForOngoingSession()
                             && isActiveWithOngoingSession;
-                    if (isHost) {
+                    if (isActiveWithOngoingSession) {
                         mCurrentActivePosition = position;
                         updateTitleIcon(R.drawable.media_output_icon_volume,
                                 mController.getColorItemContent());
                         mSubTitleText.setText(device.getSubtextString());
                         updateTwoLineLayoutContentAlpha(DEVICE_CONNECTED_ALPHA);
-                        updateEndClickAreaAsSessionEditing(device);
+                        updateEndClickAreaAsSessionEditing(device,
+                                isHost ? R.drawable.media_output_status_edit_session
+                                        : R.drawable.ic_sound_bars_anim);
                         setTwoLineLayout(device, null /* title */, true /* bFocused */,
                                 true /* showSeekBar */, false /* showProgressBar */,
                                 true /* showSubtitle */, false /* showStatus */,
                                 true /* showEndTouchArea */, false /* isFakeActive */);
                         initSeekbar(device, isCurrentSeekbarInvisible);
                     } else {
-                        if (isActiveWithOngoingSession) {
-                            //Selected device which has ongoing session, disable seekbar since we
-                            //only allow volume control on Host
+                        if (currentlyConnected) {
                             mCurrentActivePosition = position;
-                        }
-                        boolean showSeekbar =
-                                (!device.hasOngoingSession() && currentlyConnected);
-                        if (showSeekbar) {
                             updateTitleIcon(R.drawable.media_output_icon_volume,
                                     mController.getColorItemContent());
                             initSeekbar(device, isCurrentSeekbarInvisible);
@@ -222,10 +219,10 @@
                                 updateClickActionBasedOnSelectionBehavior(device)
                                         ? DEVICE_CONNECTED_ALPHA : DEVICE_DISCONNECTED_ALPHA);
                         setTwoLineLayout(device, currentlyConnected /* bFocused */,
-                                showSeekbar  /* showSeekBar */,
+                                currentlyConnected  /* showSeekBar */,
                                 false /* showProgressBar */, true /* showSubtitle */,
                                 deviceStatusIcon != null /* showStatus */,
-                                isActiveWithOngoingSession /* isFakeActive */);
+                                false /* isFakeActive */);
                     }
                 } else if (device.getState() == MediaDeviceState.STATE_CONNECTING_FAILED) {
                     setUpDeviceIcon(device);
@@ -267,25 +264,16 @@
                         setSingleLineLayout(getItemTitle(device));
                     } else if (device.hasOngoingSession()) {
                         mCurrentActivePosition = position;
-                        if (device.isHostForOngoingSession()) {
-                            updateTitleIcon(R.drawable.media_output_icon_volume,
-                                    mController.getColorItemContent());
-                            updateEndClickAreaAsSessionEditing(device);
-                            mEndClickIcon.setVisibility(View.VISIBLE);
-                            setSingleLineLayout(getItemTitle(device), true /* showSeekBar */,
-                                    false /* showProgressBar */, false /* showCheckBox */,
-                                    true /* showEndTouchArea */);
-                            initSeekbar(device, isCurrentSeekbarInvisible);
-                        } else {
-                            updateDeviceStatusIcon(mContext.getDrawable(
-                                    R.drawable.ic_sound_bars_anim));
-                            mStatusIcon.setVisibility(View.VISIBLE);
-                            updateSingleLineLayoutContentAlpha(
-                                    updateClickActionBasedOnSelectionBehavior(device)
-                                            ? DEVICE_CONNECTED_ALPHA : DEVICE_DISCONNECTED_ALPHA);
-                            setSingleLineLayout(getItemTitle(device));
-                            initFakeActiveDevice();
-                        }
+                        updateTitleIcon(R.drawable.media_output_icon_volume,
+                                mController.getColorItemContent());
+                        updateEndClickAreaAsSessionEditing(device, device.isHostForOngoingSession()
+                                ? R.drawable.media_output_status_edit_session
+                                : R.drawable.ic_sound_bars_anim);
+                        mEndClickIcon.setVisibility(View.VISIBLE);
+                        setSingleLineLayout(getItemTitle(device), true /* showSeekBar */,
+                                false /* showProgressBar */, false /* showCheckBox */,
+                                true /* showEndTouchArea */);
+                        initSeekbar(device, isCurrentSeekbarInvisible);
                     } else if (mController.isCurrentConnectedDeviceRemote()
                             && !mController.getSelectableMediaDevice().isEmpty()) {
                         //If device is connected and there's other selectable devices, layout as
@@ -362,7 +350,7 @@
             mStatusIcon.setAlpha(alphaValue);
         }
 
-        private void updateEndClickAreaAsSessionEditing(MediaDevice device) {
+        private void updateEndClickAreaAsSessionEditing(MediaDevice device, @DrawableRes int id) {
             mEndClickIcon.setOnClickListener(null);
             mEndTouchArea.setOnClickListener(null);
             updateEndClickAreaColor(mController.getColorSeekbarProgress());
@@ -371,6 +359,11 @@
             mEndClickIcon.setOnClickListener(
                     v -> mController.tryToLaunchInAppRoutingIntent(device.getId(), v));
             mEndTouchArea.setOnClickListener(v -> mEndClickIcon.performClick());
+            Drawable drawable = mContext.getDrawable(id);
+            mEndClickIcon.setImageDrawable(drawable);
+            if (drawable instanceof AnimatedVectorDrawable) {
+                ((AnimatedVectorDrawable) drawable).start();
+            }
         }
 
         public void updateEndClickAreaColor(int color) {
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/MediaProjectionTaskSwitcherCoreStartable.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/MediaProjectionTaskSwitcherCoreStartable.kt
new file mode 100644
index 0000000..3c50127
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/MediaProjectionTaskSwitcherCoreStartable.kt
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.mediaprojection.taskswitcher
+
+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.mediaprojection.taskswitcher.ui.TaskSwitcherNotificationCoordinator
+import javax.inject.Inject
+
+@SysUISingleton
+class MediaProjectionTaskSwitcherCoreStartable
+@Inject
+constructor(
+    private val notificationCoordinator: TaskSwitcherNotificationCoordinator,
+    private val featureFlags: FeatureFlags,
+) : CoreStartable {
+
+    override fun start() {
+        if (featureFlags.isEnabled(Flags.PARTIAL_SCREEN_SHARING_TASK_SWITCHER)) {
+            notificationCoordinator.start()
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/MediaProjectionTaskSwitcherModule.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/MediaProjectionTaskSwitcherModule.kt
new file mode 100644
index 0000000..22ad07e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/MediaProjectionTaskSwitcherModule.kt
@@ -0,0 +1,32 @@
+/*
+ * 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.mediaprojection.taskswitcher
+
+import com.android.systemui.mediaprojection.taskswitcher.data.repository.ActivityTaskManagerTasksRepository
+import com.android.systemui.mediaprojection.taskswitcher.data.repository.MediaProjectionManagerRepository
+import com.android.systemui.mediaprojection.taskswitcher.data.repository.MediaProjectionRepository
+import com.android.systemui.mediaprojection.taskswitcher.data.repository.TasksRepository
+import dagger.Binds
+import dagger.Module
+
+@Module
+interface MediaProjectionTaskSwitcherModule {
+
+    @Binds fun mediaRepository(impl: MediaProjectionManagerRepository): MediaProjectionRepository
+
+    @Binds fun tasksRepository(impl: ActivityTaskManagerTasksRepository): TasksRepository
+}
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/data/model/MediaProjectionState.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/data/model/MediaProjectionState.kt
new file mode 100644
index 0000000..9938f11
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/data/model/MediaProjectionState.kt
@@ -0,0 +1,26 @@
+/*
+ * 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.mediaprojection.taskswitcher.data.model
+
+import android.app.TaskInfo
+
+/** Represents the state of media projection. */
+sealed interface MediaProjectionState {
+    object NotProjecting : MediaProjectionState
+    object EntireScreen : MediaProjectionState
+    data class SingleTask(val task: TaskInfo) : MediaProjectionState
+}
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/ActivityTaskManagerTasksRepository.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/ActivityTaskManagerTasksRepository.kt
new file mode 100644
index 0000000..492d482
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/ActivityTaskManagerTasksRepository.kt
@@ -0,0 +1,75 @@
+/*
+ * 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.mediaprojection.taskswitcher.data.repository
+
+import android.app.ActivityManager.RunningTaskInfo
+import android.app.ActivityTaskManager
+import android.app.TaskStackListener
+import android.os.IBinder
+import android.util.Log
+import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
+import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dagger.qualifiers.Background
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.shareIn
+import kotlinx.coroutines.withContext
+
+/** Implementation of [TasksRepository] that uses [ActivityTaskManager] as the data source. */
+@SysUISingleton
+class ActivityTaskManagerTasksRepository
+@Inject
+constructor(
+    private val activityTaskManager: ActivityTaskManager,
+    @Application private val applicationScope: CoroutineScope,
+    @Background private val backgroundDispatcher: CoroutineDispatcher,
+) : TasksRepository {
+
+    override suspend fun findRunningTaskFromWindowContainerToken(
+        windowContainerToken: IBinder
+    ): RunningTaskInfo? =
+        getRunningTasks().firstOrNull { taskInfo ->
+            taskInfo.token.asBinder() == windowContainerToken
+        }
+
+    private suspend fun getRunningTasks(): List<RunningTaskInfo> =
+        withContext(backgroundDispatcher) { activityTaskManager.getTasks(Integer.MAX_VALUE) }
+
+    override val foregroundTask: Flow<RunningTaskInfo> =
+        conflatedCallbackFlow {
+                val listener =
+                    object : TaskStackListener() {
+                        override fun onTaskMovedToFront(taskInfo: RunningTaskInfo) {
+                            Log.d(TAG, "onTaskMovedToFront: $taskInfo")
+                            trySendWithFailureLogging(taskInfo, TAG)
+                        }
+                    }
+                activityTaskManager.registerTaskStackListener(listener)
+                awaitClose { activityTaskManager.unregisterTaskStackListener(listener) }
+            }
+            .shareIn(applicationScope, SharingStarted.Lazily, replay = 1)
+
+    companion object {
+        private const val TAG = "TasksRepository"
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/MediaProjectionManagerRepository.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/MediaProjectionManagerRepository.kt
new file mode 100644
index 0000000..38d4e69
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/MediaProjectionManagerRepository.kt
@@ -0,0 +1,92 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.mediaprojection.taskswitcher.data.repository
+
+import android.media.projection.MediaProjectionInfo
+import android.media.projection.MediaProjectionManager
+import android.os.Handler
+import android.util.Log
+import android.view.ContentRecordingSession
+import android.view.ContentRecordingSession.RECORD_CONTENT_DISPLAY
+import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
+import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.mediaprojection.taskswitcher.data.model.MediaProjectionState
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.shareIn
+import kotlinx.coroutines.launch
+
+@SysUISingleton
+class MediaProjectionManagerRepository
+@Inject
+constructor(
+    private val mediaProjectionManager: MediaProjectionManager,
+    @Main private val handler: Handler,
+    @Application private val applicationScope: CoroutineScope,
+    private val tasksRepository: TasksRepository,
+) : MediaProjectionRepository {
+
+    override val mediaProjectionState: Flow<MediaProjectionState> =
+        conflatedCallbackFlow {
+                val callback =
+                    object : MediaProjectionManager.Callback() {
+                        override fun onStart(info: MediaProjectionInfo?) {
+                            Log.d(TAG, "MediaProjectionManager.Callback#onStart")
+                            trySendWithFailureLogging(MediaProjectionState.NotProjecting, TAG)
+                        }
+
+                        override fun onStop(info: MediaProjectionInfo?) {
+                            Log.d(TAG, "MediaProjectionManager.Callback#onStop")
+                            trySendWithFailureLogging(MediaProjectionState.NotProjecting, TAG)
+                        }
+
+                        override fun onRecordingSessionSet(
+                            info: MediaProjectionInfo,
+                            session: ContentRecordingSession?
+                        ) {
+                            Log.d(TAG, "MediaProjectionManager.Callback#onSessionStarted: $session")
+                            launch { trySendWithFailureLogging(stateForSession(session), TAG) }
+                        }
+                    }
+                mediaProjectionManager.addCallback(callback, handler)
+                awaitClose { mediaProjectionManager.removeCallback(callback) }
+            }
+            .shareIn(scope = applicationScope, started = SharingStarted.Lazily, replay = 1)
+
+    private suspend fun stateForSession(session: ContentRecordingSession?): MediaProjectionState {
+        if (session == null) {
+            return MediaProjectionState.NotProjecting
+        }
+        if (session.contentToRecord == RECORD_CONTENT_DISPLAY || session.tokenToRecord == null) {
+            return MediaProjectionState.EntireScreen
+        }
+        val matchingTask =
+            tasksRepository.findRunningTaskFromWindowContainerToken(session.tokenToRecord)
+                ?: return MediaProjectionState.EntireScreen
+        return MediaProjectionState.SingleTask(matchingTask)
+    }
+
+    companion object {
+        private const val TAG = "MediaProjectionMngrRepo"
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/MediaProjectionRepository.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/MediaProjectionRepository.kt
new file mode 100644
index 0000000..5bec692
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/MediaProjectionRepository.kt
@@ -0,0 +1,27 @@
+/*
+ * 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.mediaprojection.taskswitcher.data.repository
+
+import com.android.systemui.mediaprojection.taskswitcher.data.model.MediaProjectionState
+import kotlinx.coroutines.flow.Flow
+
+/** Represents a repository to retrieve and change data related to media projection. */
+interface MediaProjectionRepository {
+
+    /** Represents the current [MediaProjectionState]. */
+    val mediaProjectionState: Flow<MediaProjectionState>
+}
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/NoOpMediaProjectionRepository.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/NoOpMediaProjectionRepository.kt
new file mode 100644
index 0000000..544eb6b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/NoOpMediaProjectionRepository.kt
@@ -0,0 +1,33 @@
+/*
+ * 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.mediaprojection.taskswitcher.data.repository
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.mediaprojection.taskswitcher.data.model.MediaProjectionState
+import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.emptyFlow
+
+/**
+ * No-op implementation of [MediaProjectionRepository] that does nothing. Currently used as a
+ * placeholder, while the real implementation is not completed.
+ */
+@SysUISingleton
+class NoOpMediaProjectionRepository @Inject constructor() : MediaProjectionRepository {
+
+    override val mediaProjectionState: Flow<MediaProjectionState> = emptyFlow()
+}
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/TasksRepository.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/TasksRepository.kt
new file mode 100644
index 0000000..6a535e4
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/TasksRepository.kt
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.mediaprojection.taskswitcher.data.repository
+
+import android.app.ActivityManager.RunningTaskInfo
+import android.os.IBinder
+import kotlinx.coroutines.flow.Flow
+
+/** Repository responsible for retrieving data related to running tasks. */
+interface TasksRepository {
+
+    /**
+     * Tries to find a [RunningTaskInfo] with a matching window container token. Returns `null` when
+     * no matching task was found.
+     */
+    suspend fun findRunningTaskFromWindowContainerToken(
+        windowContainerToken: IBinder
+    ): RunningTaskInfo?
+
+    /**
+     * Emits a stream of [RunningTaskInfo] that have been moved to the foreground.
+     *
+     * Note: when subscribing for the first time, it will not immediately emit the current
+     * foreground task. Only after a change in foreground task has occurred.
+     */
+    val foregroundTask: Flow<RunningTaskInfo>
+}
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/domain/interactor/TaskSwitchInteractor.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/domain/interactor/TaskSwitchInteractor.kt
new file mode 100644
index 0000000..fc5cf7d
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/domain/interactor/TaskSwitchInteractor.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.mediaprojection.taskswitcher.domain.interactor
+
+import android.app.TaskInfo
+import android.content.Intent
+import android.util.Log
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.mediaprojection.taskswitcher.data.model.MediaProjectionState
+import com.android.systemui.mediaprojection.taskswitcher.data.repository.MediaProjectionRepository
+import com.android.systemui.mediaprojection.taskswitcher.data.repository.TasksRepository
+import com.android.systemui.mediaprojection.taskswitcher.domain.model.TaskSwitchState
+import javax.inject.Inject
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.map
+
+/** Interactor with logic related to task switching in the context of media projection. */
+@OptIn(ExperimentalCoroutinesApi::class)
+@SysUISingleton
+class TaskSwitchInteractor
+@Inject
+constructor(
+    mediaProjectionRepository: MediaProjectionRepository,
+    private val tasksRepository: TasksRepository,
+) {
+
+    /**
+     * Emits a stream of changes to the state of task switching, in the context of media projection.
+     */
+    val taskSwitchChanges: Flow<TaskSwitchState> =
+        mediaProjectionRepository.mediaProjectionState.flatMapLatest { projectionState ->
+            Log.d(TAG, "MediaProjectionState -> $projectionState")
+            when (projectionState) {
+                is MediaProjectionState.SingleTask -> {
+                    val projectedTask = projectionState.task
+                    tasksRepository.foregroundTask.map { foregroundTask ->
+                        if (hasForegroundTaskSwitched(projectedTask, foregroundTask)) {
+                            TaskSwitchState.TaskSwitched(projectedTask, foregroundTask)
+                        } else {
+                            TaskSwitchState.TaskUnchanged
+                        }
+                    }
+                }
+                is MediaProjectionState.EntireScreen,
+                is MediaProjectionState.NotProjecting -> {
+                    flowOf(TaskSwitchState.NotProjectingTask)
+                }
+            }
+        }
+
+    /**
+     * Returns whether tasks have been switched.
+     *
+     * Always returns `false` when launcher is in the foreground. The reason is that when going to
+     * recents to switch apps, launcher becomes the new foreground task, and we don't want to show
+     * the notification then.
+     */
+    private fun hasForegroundTaskSwitched(projectedTask: TaskInfo, foregroundTask: TaskInfo) =
+        projectedTask.taskId != foregroundTask.taskId && !foregroundTask.isLauncher
+
+    private val TaskInfo.isLauncher
+        get() =
+            baseIntent.hasCategory(Intent.CATEGORY_HOME) && baseIntent.action == Intent.ACTION_MAIN
+
+    companion object {
+        private const val TAG = "TaskSwitchInteractor"
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/domain/model/TaskSwitchState.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/domain/model/TaskSwitchState.kt
new file mode 100644
index 0000000..cd1258e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/domain/model/TaskSwitchState.kt
@@ -0,0 +1,30 @@
+/*
+ * 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.mediaprojection.taskswitcher.domain.model
+
+import android.app.TaskInfo
+
+/** Represents tha state of task switching in the context of single task media projection. */
+sealed interface TaskSwitchState {
+    /** Currently no task is being projected. */
+    object NotProjectingTask : TaskSwitchState
+    /** The foreground task is the same as the task that is currently being projected. */
+    object TaskUnchanged : TaskSwitchState
+    /** The foreground task is a different one to the task it currently being projected. */
+    data class TaskSwitched(val projectedTask: TaskInfo, val foregroundTask: TaskInfo) :
+        TaskSwitchState
+}
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/ui/TaskSwitcherNotificationCoordinator.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/ui/TaskSwitcherNotificationCoordinator.kt
new file mode 100644
index 0000000..a4f4076
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/ui/TaskSwitcherNotificationCoordinator.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.mediaprojection.taskswitcher.ui
+
+import android.content.Context
+import android.util.Log
+import android.widget.Toast
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.mediaprojection.taskswitcher.ui.model.TaskSwitcherNotificationUiState.NotShowing
+import com.android.systemui.mediaprojection.taskswitcher.ui.model.TaskSwitcherNotificationUiState.Showing
+import com.android.systemui.mediaprojection.taskswitcher.ui.viewmodel.TaskSwitcherNotificationViewModel
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.flowOn
+import kotlinx.coroutines.launch
+
+/** Coordinator responsible for showing/hiding the task switcher notification. */
+@SysUISingleton
+class TaskSwitcherNotificationCoordinator
+@Inject
+constructor(
+    private val context: Context,
+    @Application private val applicationScope: CoroutineScope,
+    @Main private val mainDispatcher: CoroutineDispatcher,
+    private val viewModel: TaskSwitcherNotificationViewModel,
+) {
+
+    fun start() {
+        applicationScope.launch {
+            viewModel.uiState.flowOn(mainDispatcher).collect { uiState ->
+                Log.d(TAG, "uiState -> $uiState")
+                when (uiState) {
+                    is Showing -> showNotification(uiState)
+                    is NotShowing -> hideNotification()
+                }
+            }
+        }
+    }
+
+    private fun showNotification(uiState: Showing) {
+        val text =
+            """
+            Sharing pauses when you switch apps.
+            Share this app instead.
+            Switch back.
+            """
+                .trimIndent()
+        // TODO(b/286201515): Create actual notification.
+        Toast.makeText(context, text, Toast.LENGTH_SHORT).show()
+    }
+
+    private fun hideNotification() {}
+
+    companion object {
+        private const val TAG = "TaskSwitchNotifCoord"
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/ui/model/TaskSwitcherNotificationUiState.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/ui/model/TaskSwitcherNotificationUiState.kt
new file mode 100644
index 0000000..21aee72
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/ui/model/TaskSwitcherNotificationUiState.kt
@@ -0,0 +1,30 @@
+/*
+ * 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.mediaprojection.taskswitcher.ui.model
+
+import android.app.TaskInfo
+
+/** Represents the UI state for the task switcher notification. */
+sealed interface TaskSwitcherNotificationUiState {
+    /** The notification should not be shown. */
+    object NotShowing : TaskSwitcherNotificationUiState
+    /** The notification should be shown. */
+    data class Showing(
+        val projectedTask: TaskInfo,
+        val foregroundTask: TaskInfo,
+    ) : TaskSwitcherNotificationUiState
+}
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/ui/viewmodel/TaskSwitcherNotificationViewModel.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/ui/viewmodel/TaskSwitcherNotificationViewModel.kt
new file mode 100644
index 0000000..d9754d4
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/ui/viewmodel/TaskSwitcherNotificationViewModel.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.mediaprojection.taskswitcher.ui.viewmodel
+
+import android.util.Log
+import com.android.systemui.mediaprojection.taskswitcher.domain.interactor.TaskSwitchInteractor
+import com.android.systemui.mediaprojection.taskswitcher.domain.model.TaskSwitchState
+import com.android.systemui.mediaprojection.taskswitcher.ui.model.TaskSwitcherNotificationUiState
+import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.map
+
+class TaskSwitcherNotificationViewModel @Inject constructor(interactor: TaskSwitchInteractor) {
+
+    val uiState: Flow<TaskSwitcherNotificationUiState> =
+        interactor.taskSwitchChanges.map { taskSwitchChange ->
+            Log.d(TAG, "taskSwitchChange: $taskSwitchChange")
+            when (taskSwitchChange) {
+                is TaskSwitchState.TaskSwitched -> {
+                    TaskSwitcherNotificationUiState.Showing(
+                        projectedTask = taskSwitchChange.projectedTask,
+                        foregroundTask = taskSwitchChange.foregroundTask,
+                    )
+                }
+                is TaskSwitchState.NotProjectingTask,
+                is TaskSwitchState.TaskUnchanged -> {
+                    TaskSwitcherNotificationUiState.NotShowing
+                }
+            }
+        }
+
+    companion object {
+        private const val TAG = "TaskSwitchNotifVM"
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/model/SysUiState.java b/packages/SystemUI/src/com/android/systemui/model/SysUiState.java
index 8d80990..07846b5 100644
--- a/packages/SystemUI/src/com/android/systemui/model/SysUiState.java
+++ b/packages/SystemUI/src/com/android/systemui/model/SysUiState.java
@@ -114,7 +114,7 @@
         pw.print("  mSysUiStateFlags="); pw.println(mFlags);
         pw.println("    " + QuickStepContract.getSystemUiStateString(mFlags));
         pw.print("    backGestureDisabled=");
-        pw.println(QuickStepContract.isBackGestureDisabled(mFlags));
+        pw.println(QuickStepContract.isBackGestureDisabled(mFlags, false /* forTrackpad */));
         pw.print("    assistantGestureDisabled=");
         pw.println(QuickStepContract.isAssistantGestureDisabled(mFlags));
     }
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationModeController.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationModeController.java
index 63276fe..99daf36 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationModeController.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationModeController.java
@@ -28,18 +28,21 @@
 import android.os.PatternMatcher;
 import android.os.RemoteException;
 import android.os.ServiceManager;
+import android.os.Trace;
 import android.os.UserHandle;
 import android.provider.Settings;
 import android.provider.Settings.Secure;
 import android.util.Log;
 
+import androidx.annotation.NonNull;
+
 import com.android.systemui.Dumpable;
 import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.dagger.qualifiers.UiBackground;
 import com.android.systemui.dump.DumpManager;
-import com.android.systemui.shared.system.ActivityManagerWrapper;
+import com.android.systemui.settings.UserTracker;
 import com.android.systemui.statusbar.policy.ConfigurationController;
-import com.android.systemui.statusbar.policy.DeviceProvisionedController;
 
 import java.io.PrintWriter;
 import java.util.ArrayList;
@@ -64,22 +67,21 @@
     private Context mCurrentUserContext;
     private final IOverlayManager mOverlayManager;
     private final Executor mUiBgExecutor;
+    private final UserTracker mUserTracker;
 
     private ArrayList<ModeChangedListener> mListeners = new ArrayList<>();
 
-    private final DeviceProvisionedController.DeviceProvisionedListener mDeviceProvisionedCallback =
-            new DeviceProvisionedController.DeviceProvisionedListener() {
-                @Override
-                public void onUserSwitched() {
-                    if (DEBUG) {
-                        Log.d(TAG, "onUserSwitched: "
-                                + ActivityManagerWrapper.getInstance().getCurrentUserId());
-                    }
+    private final UserTracker.Callback mUserTrackerCallback = new UserTracker.Callback() {
+        @Override
+        public void onUserChanged(int newUser, @NonNull Context userContext) {
+            if (DEBUG) {
+                Log.d(TAG, "onUserChanged: "
+                        + newUser);
+            }
 
-                    // Update the nav mode for the current user
-                    updateCurrentInteractionMode(true /* notify */);
-                }
-            };
+            updateCurrentInteractionMode(true /* notify */);
+        }
+    };
 
     // The primary user SysUI process doesn't get AppInfo changes from overlay package changes for
     // the secondary user (b/158613864), so we need to update the interaction mode here as well
@@ -97,19 +99,20 @@
 
     @Inject
     public NavigationModeController(Context context,
-            DeviceProvisionedController deviceProvisionedController,
             ConfigurationController configurationController,
+            UserTracker userTracker,
+            @Main Executor mainExecutor,
             @UiBackground Executor uiBgExecutor,
             DumpManager dumpManager) {
         mContext = context;
         mCurrentUserContext = context;
+        mUserTracker = userTracker;
+        mUserTracker.addCallback(mUserTrackerCallback, mainExecutor);
         mOverlayManager = IOverlayManager.Stub.asInterface(
                 ServiceManager.getService(Context.OVERLAY_SERVICE));
         mUiBgExecutor = uiBgExecutor;
         dumpManager.registerDumpable(getClass().getSimpleName(), this);
 
-        deviceProvisionedController.addCallback(mDeviceProvisionedCallback);
-
         IntentFilter overlayFilter = new IntentFilter(ACTION_OVERLAY_CHANGED);
         overlayFilter.addDataScheme("package");
         overlayFilter.addDataSchemeSpecificPart("android", PatternMatcher.PATTERN_LITERAL);
@@ -129,6 +132,7 @@
     }
 
     public void updateCurrentInteractionMode(boolean notify) {
+        Trace.beginSection("NMC#updateCurrentInteractionMode");
         mCurrentUserContext = getCurrentUserContext();
         int mode = getCurrentInteractionMode(mCurrentUserContext);
         mUiBgExecutor.execute(() ->
@@ -144,6 +148,7 @@
                 mListeners.get(i).onNavigationModeChanged(mode);
             }
         }
+        Trace.endSection();
     }
 
     public int addListener(ModeChangedListener listener) {
@@ -171,7 +176,7 @@
     }
 
     public Context getCurrentUserContext() {
-        int userId = ActivityManagerWrapper.getInstance().getCurrentUserId();
+        int userId = mUserTracker.getUserId();
         if (DEBUG) {
             Log.d(TAG, "getCurrentUserContext: contextUser=" + mContext.getUserId()
                     + " currentUser=" + userId);
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
index 7b86d0a..a8af67a 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
@@ -1023,7 +1023,8 @@
             boolean isWithinInsets = isWithinInsets((int) ev.getX(), (int) ev.getY());
             boolean isBackAllowedCommon = !mDisabledForQuickstep && mIsBackGestureAllowed
                     && !mGestureBlockingActivityRunning
-                    && !QuickStepContract.isBackGestureDisabled(mSysUiFlags)
+                    && !QuickStepContract.isBackGestureDisabled(mSysUiFlags,
+                            mIsTrackpadThreeFingerSwipe)
                     && !isTrackpadScroll(mIsTrackpadGestureFeaturesEnabled, ev);
             if (mIsTrackpadThreeFingerSwipe) {
                 // Trackpad back gestures don't have zones, so we don't need to check if the down
@@ -1056,8 +1057,9 @@
                             + " disp=%s, wl=%d, il=%d, wr=%d, ir=%d, excl=%s]",
                     curTime, curTimeStr, mAllowGesture, mIsTrackpadThreeFingerSwipe,
                     mIsOnLeftEdge, mDeferSetIsOnLeftEdge, mIsBackGestureAllowed,
-                    QuickStepContract.isBackGestureDisabled(mSysUiFlags), mDisabledForQuickstep,
-                    mGestureBlockingActivityRunning, mIsInPip, mDisplaySize,
+                    QuickStepContract.isBackGestureDisabled(mSysUiFlags,
+                            mIsTrackpadThreeFingerSwipe),
+                    mDisabledForQuickstep, mGestureBlockingActivityRunning, mIsInPip, mDisplaySize,
                     mEdgeWidthLeft, mLeftInset, mEdgeWidthRight, mRightInset, mExcludeRegion));
         } else if (mAllowGesture || mLogGesture) {
             if (!mThresholdCrossed) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/InstalledTilesComponentRepository.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/InstalledTilesComponentRepository.kt
index 498f403..6e7e099 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/InstalledTilesComponentRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/InstalledTilesComponentRepository.kt
@@ -51,12 +51,26 @@
 @Inject
 constructor(
     @Application private val applicationContext: Context,
-    private val packageManager: PackageManager,
     @Background private val backgroundDispatcher: CoroutineDispatcher,
 ) : InstalledTilesComponentRepository {
 
-    override fun getInstalledTilesComponents(userId: Int): Flow<Set<ComponentName>> =
-        conflatedCallbackFlow {
+    override fun getInstalledTilesComponents(userId: Int): Flow<Set<ComponentName>> {
+        /*
+         * In order to query [PackageManager] for different users, this implementation will call
+         * [Context.createContextAsUser] and retrieve the [PackageManager] from that context.
+         */
+        val packageManager =
+            if (applicationContext.userId == userId) {
+                applicationContext.packageManager
+            } else {
+                applicationContext
+                    .createContextAsUser(
+                        UserHandle.of(userId),
+                        /* flags */ 0,
+                    )
+                    .packageManager
+            }
+        return conflatedCallbackFlow {
                 val receiver =
                     object : BroadcastReceiver() {
                         override fun onReceive(context: Context?, intent: Intent?) {
@@ -74,12 +88,13 @@
                 awaitClose { applicationContext.unregisterReceiver(receiver) }
             }
             .onStart { emit(Unit) }
-            .map { reloadComponents(userId) }
+            .map { reloadComponents(userId, packageManager) }
             .distinctUntilChanged()
             .flowOn(backgroundDispatcher)
+    }
 
     @WorkerThread
-    private fun reloadComponents(userId: Int): Set<ComponentName> {
+    private fun reloadComponents(userId: Int, packageManager: PackageManager): Set<ComponentName> {
         return packageManager
             .queryIntentServicesAsUser(INTENT, FLAGS, userId)
             .mapNotNull { it.serviceInfo }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java
index 2a9e7d0..1ca2a96 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java
@@ -330,7 +330,9 @@
         final int eventId = mClickEventId++;
         mQSLogger.logTileLongClick(mTileSpec, mStatusBarStateController.getState(), mState.state,
                 eventId);
-        mHandler.obtainMessage(H.LONG_CLICK, eventId, 0, view).sendToTarget();
+        if (!mFalsingManager.isFalseLongTap(FalsingManager.LOW_PENALTY)) {
+            mHandler.obtainMessage(H.LONG_CLICK, eventId, 0, view).sendToTarget();
+        }
     }
 
     public LogMaker populate(LogMaker logMaker) {
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ActionProxyReceiver.java b/packages/SystemUI/src/com/android/systemui/screenshot/ActionProxyReceiver.java
index 3aefcb3..7e234ae 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ActionProxyReceiver.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ActionProxyReceiver.java
@@ -71,6 +71,8 @@
             ActivityOptions opts = ActivityOptions.makeBasic();
             opts.setDisallowEnterPictureInPictureWhileLaunching(
                     intent.getBooleanExtra(EXTRA_DISALLOW_ENTER_PIP, false));
+            opts.setPendingIntentBackgroundActivityStartMode(
+                    ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED);
             try {
                 actionIntent.send(context, 0, null, null, null, null, opts.toBundle());
                 if (intent.getBooleanExtra(ScreenshotController.EXTRA_OVERRIDE_TRANSITION, false)) {
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/OverlayActionChip.java b/packages/SystemUI/src/com/android/systemui/screenshot/OverlayActionChip.java
index 13678b0..9e8ea3a 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/OverlayActionChip.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/OverlayActionChip.java
@@ -99,6 +99,8 @@
             try {
                 BroadcastOptions options = BroadcastOptions.makeBasic();
                 options.setInteractive(true);
+                options.setPendingIntentBackgroundActivityStartMode(
+                        BroadcastOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED);
                 intent.send(options.toBundle());
                 finisher.run();
             } catch (PendingIntent.CanceledException e) {
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/SmartActionsReceiver.java b/packages/SystemUI/src/com/android/systemui/screenshot/SmartActionsReceiver.java
index 9761f59..ef58b9d 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/SmartActionsReceiver.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/SmartActionsReceiver.java
@@ -55,7 +55,8 @@
             Log.d(TAG, "Executing smart action [" + actionType + "]:" + pendingIntent.getIntent());
         }
         ActivityOptions opts = ActivityOptions.makeBasic();
-
+        opts.setPendingIntentBackgroundActivityStartMode(
+                ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED);
         try {
             pendingIntent.send(context, 0, fillIn, null, null, null, opts.toBundle());
         } catch (PendingIntent.CanceledException e) {
diff --git a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessDialog.java b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessDialog.java
index 8879501..5199bd4 100644
--- a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessDialog.java
@@ -84,6 +84,7 @@
         window.getDecorView();
         window.setLayout(
                 WindowManager.LayoutParams.MATCH_PARENT, WindowManager.LayoutParams.WRAP_CONTENT);
+        getTheme().applyStyle(R.style.Theme_SystemUI_QuickSettings, false);
 
         setContentView(R.layout.brightness_mirror_container);
         FrameLayout frame = findViewById(R.id.brightness_mirror_container);
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
index 44436b9..d97db3b 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
@@ -1175,6 +1175,7 @@
                 mKeyguardStatusViewComponentFactory.build(keyguardStatusView);
         mKeyguardStatusViewController = statusViewComponent.getKeyguardStatusViewController();
         mKeyguardStatusViewController.init();
+        mKeyguardStatusViewController.setSplitShadeEnabled(mSplitShadeEnabled);
         updateClockAppearance();
 
         if (mKeyguardUserSwitcherController != null) {
@@ -1227,6 +1228,7 @@
 
     private void onSplitShadeEnabledChanged() {
         mShadeLog.logSplitShadeChanged(mSplitShadeEnabled);
+        mKeyguardStatusViewController.setSplitShadeEnabled(mSplitShadeEnabled);
         // Reset any left over overscroll state. It is a rare corner case but can happen.
         mQsController.setOverScrollAmount(0);
         mScrimController.setNotificationsOverScrollAmount(0);
@@ -1625,6 +1627,7 @@
 
         mWillPlayDelayedDozeAmountAnimation = willPlay;
         mWakeUpCoordinator.logDelayingClockWakeUpAnimation(willPlay);
+        mKeyguardMediaController.setDozeWakeUpAnimationWaiting(willPlay);
 
         // Once changing this value, see if we should move the clock.
         positionClockAndNotifications();
diff --git a/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java b/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java
index fe1b365..025c4611 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java
@@ -1220,14 +1220,15 @@
         if (mIsFullWidth) {
             clipStatusView = qsVisible;
             float screenCornerRadius =
-                    !mSplitShadeEnabled || mRecordingController.isRecording()
-                            || mCastController.hasConnectedCastDevice()
+                    mRecordingController.isRecording() || mCastController.hasConnectedCastDevice()
                             ? 0 : mScreenCornerRadius;
             radius = (int) MathUtils.lerp(screenCornerRadius, mScrimCornerRadius,
                     Math.min(top / (float) mScrimCornerRadius, 1f));
 
-            float bottomRadius = mExpanded ? screenCornerRadius :
-                    calculateBottomCornerRadius(screenCornerRadius);
+            float bottomRadius = mSplitShadeEnabled ? screenCornerRadius : 0;
+            if (!mExpanded) {
+                bottomRadius = calculateBottomCornerRadius(bottomRadius);
+            }
             mScrimController.setNotificationBottomRadius(bottomRadius);
         }
         if (isQsFragmentCreated()) {
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeModule.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeModule.kt
index 0500a58..8ae9e5e 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeModule.kt
@@ -215,9 +215,15 @@
         @Provides
         @SysUISingleton
         fun providesLockIconView(
-            notificationShadeWindowView: NotificationShadeWindowView,
+            keyguardRootView: KeyguardRootView,
+            notificationPanelView: NotificationPanelView,
+            featureFlags: FeatureFlags
         ): LockIconView {
-            return notificationShadeWindowView.findViewById(R.id.lock_icon_view)
+            if (featureFlags.isEnabled(Flags.MIGRATE_LOCK_ICON)) {
+                return keyguardRootView.findViewById(R.id.lock_icon_view)
+            } else {
+                return notificationPanelView.findViewById(R.id.lock_icon_view)
+            }
         }
 
         // TODO(b/277762009): Only allow this view's controller to inject the view. See above.
diff --git a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractor.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractor.kt
index eceda84..6fde84a 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractor.kt
@@ -17,38 +17,52 @@
 package com.android.systemui.shade.domain.interactor
 
 import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.keyguard.data.repository.KeyguardRepository
 import com.android.systemui.statusbar.disableflags.data.repository.DisableFlagsRepository
 import com.android.systemui.statusbar.pipeline.mobile.data.repository.UserSetupRepository
 import com.android.systemui.statusbar.policy.DeviceProvisionedController
 import com.android.systemui.user.domain.interactor.UserInteractor
 import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.flow.Flow
+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
 
 /** Business logic for shade interactions. */
 @SysUISingleton
 class ShadeInteractor
 @Inject
 constructor(
+    @Application scope: CoroutineScope,
     disableFlagsRepository: DisableFlagsRepository,
     keyguardRepository: KeyguardRepository,
     userSetupRepository: UserSetupRepository,
     deviceProvisionedController: DeviceProvisionedController,
     userInteractor: UserInteractor,
 ) {
+    /** Emits true if the shade is currently allowed and false otherwise. */
+    val isShadeEnabled: StateFlow<Boolean> =
+        disableFlagsRepository.disableFlags
+            .map { it.isShadeEnabled() }
+            .stateIn(scope, SharingStarted.Eagerly, initialValue = false)
+
     /** Emits true if the shade can be expanded from QQS to QS and false otherwise. */
     val isExpandToQsEnabled: Flow<Boolean> =
         combine(
             disableFlagsRepository.disableFlags,
+            isShadeEnabled,
             keyguardRepository.isDozing,
             userSetupRepository.isUserSetupFlow,
-        ) { disableFlags, isDozing, isUserSetup ->
+        ) { disableFlags, isShadeEnabled, isDozing, isUserSetup ->
             deviceProvisionedController.isDeviceProvisioned &&
                 // Disallow QS during setup if it's a simple user switcher. (The user intends to
                 // use the lock screen user switcher, QS is not needed.)
                 (isUserSetup || !userInteractor.isSimpleUserSwitcher) &&
-                disableFlags.isShadeEnabled() &&
+                isShadeEnabled &&
                 disableFlags.isQuickSettingsEnabled() &&
                 !isDozing
         }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt
index e2d2ac0..4710574 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt
@@ -32,6 +32,7 @@
 import com.android.systemui.power.domain.interactor.PowerInteractor
 import com.android.systemui.shade.ShadeViewController
 import com.android.systemui.shade.data.repository.ShadeRepository
+import com.android.systemui.shade.domain.interactor.ShadeInteractor
 import com.android.systemui.statusbar.notification.collection.NotificationEntry
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
 import com.android.systemui.statusbar.notification.row.ExpandableView
@@ -76,6 +77,7 @@
     dumpManager: DumpManager,
     qsTransitionControllerFactory: LockscreenShadeQsTransitionController.Factory,
     private val shadeRepository: ShadeRepository,
+    private val shadeInteractor: ShadeInteractor,
     private val powerInteractor: PowerInteractor,
 ) : Dumpable {
     private var pulseHeight: Float = 0f
@@ -558,7 +560,7 @@
         animationHandler: ((Long) -> Unit)? = null,
         cancelAction: Runnable? = null
     ) {
-        if (centralSurfaces.isShadeDisabled) {
+        if (!shadeInteractor.isShadeEnabled.value) {
             cancelAction?.run()
             logger.logShadeDisabledOnGoToLockedShade()
             return
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java
index e6e3e7e..ea5ca27 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java
@@ -19,6 +19,7 @@
 
 import static com.android.systemui.DejankUtils.whitelistIpcs;
 
+import android.app.ActivityOptions;
 import android.app.KeyguardManager;
 import android.app.Notification;
 import android.app.admin.DevicePolicyManager;
@@ -158,7 +159,11 @@
                     final String notificationKey = intent.getStringExtra(Intent.EXTRA_INDEX);
                     if (intentSender != null) {
                         try {
-                            mContext.startIntentSender(intentSender, null, 0, 0, 0);
+                            ActivityOptions options = ActivityOptions.makeBasic();
+                            options.setPendingIntentBackgroundActivityStartMode(
+                                    ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED);
+                            mContext.startIntentSender(intentSender, null, 0, 0, 0,
+                                    options.toBundle());
                         } catch (IntentSender.SendIntentException e) {
                             /* ignore */
                         }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/commandline/CommandParser.kt b/packages/SystemUI/src/com/android/systemui/statusbar/commandline/CommandParser.kt
new file mode 100644
index 0000000..de369c3
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/commandline/CommandParser.kt
@@ -0,0 +1,327 @@
+/*
+ * 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.commandline
+
+/**
+ * [CommandParser] defines the collection of tokens which can be parsed from an incoming command
+ * list, and parses them into their respective containers. Supported tokens are of the following
+ * forms:
+ * ```
+ * Flag: boolean value, false by default. always optional.
+ * Param: named parameter, taking N args all of a given type. Currently only single arg parameters
+ *        are supported.
+ * SubCommand: named command created by adding a command to a parent. Supports all fields above, but
+ *             not other subcommands.
+ * ```
+ *
+ * Tokens are added via the factory methods for each token type. They can be made `required` by
+ * calling the [require] method for the appropriate type, as follows:
+ * ```
+ * val requiredParam = parser.require(parser.param(...))
+ * ```
+ *
+ * The reason for having an explicit require is so that generic type arguments can be handled
+ * properly. See [SingleArgParam] and [SingleArgParamOptional] for the difference between an
+ * optional parameter and a required one.
+ *
+ * Typical usage of a required parameter, however, will occur within the context of a
+ * [ParseableCommand], which defines a convenience `require()` method:
+ * ```
+ * class MyCommand : ParseableCommand {
+ *   val requiredParam = param(...).require()
+ * }
+ * ```
+ *
+ * This parser defines two modes of parsing, both of which validate for required parameters.
+ * 1. [parse] is a top-level parsing method. This parser will walk the given arg list and populate
+ *    all of the delegate classes based on their type. It will handle SubCommands, and after parsing
+ *    will check for any required-but-missing SubCommands or Params.
+ *
+ *    **This method requires that every received token is represented in its grammar.**
+ * 2. [parseAsSubCommand] is a second-level parsing method suitable for any [SubCommand]. This
+ *    method will handle _only_ flags and params. It will return parsing control to its parent
+ *    parser on the first unknown token rather than throwing.
+ */
+class CommandParser {
+    private val _flags = mutableListOf<Flag>()
+    val flags: List<Flag> = _flags
+    private val _params = mutableListOf<Param>()
+    val params: List<Param> = _params
+    private val _subCommands = mutableListOf<SubCommand>()
+    val subCommands: List<SubCommand> = _subCommands
+
+    private val tokenSet = mutableSetOf<String>()
+
+    /**
+     * Parse the arg list into the fields defined in the containing class.
+     *
+     * @return true if all required fields are present after parsing
+     * @throws ArgParseError on any failure to process args
+     */
+    fun parse(args: List<String>): Boolean {
+        if (args.isEmpty()) {
+            return false
+        }
+
+        val iterator = args.listIterator()
+        var tokenHandled: Boolean
+        while (iterator.hasNext()) {
+            val token = iterator.next()
+            tokenHandled = false
+
+            flags
+                .find { it.matches(token) }
+                ?.let {
+                    it.inner = true
+                    tokenHandled = true
+                }
+
+            if (tokenHandled) continue
+
+            params
+                .find { it.matches(token) }
+                ?.let {
+                    it.parseArgsFromIter(iterator)
+                    tokenHandled = true
+                }
+
+            if (tokenHandled) continue
+
+            subCommands
+                .find { it.matches(token) }
+                ?.let {
+                    it.parseSubCommandArgs(iterator)
+                    tokenHandled = true
+                }
+
+            if (!tokenHandled) {
+                throw ArgParseError("Unknown token: $token")
+            }
+        }
+
+        return validateRequiredParams()
+    }
+
+    /**
+     * Parse a subset of the commands that came in from the top-level [parse] method, for the
+     * subcommand that this parser represents. Note that subcommands may not contain other
+     * subcommands. But they may contain flags and params.
+     *
+     * @return true if all required fields are present after parsing
+     * @throws ArgParseError on any failure to process args
+     */
+    fun parseAsSubCommand(iter: ListIterator<String>): Boolean {
+        // arg[-1] is our subcommand name, so the rest of the args are either for this
+        // subcommand, OR for the top-level command to handle. Therefore, we bail on the first
+        // failure, but still check our own required params
+
+        // The mere presence of a subcommand (similar to a flag) is a valid subcommand
+        if (flags.isEmpty() && params.isEmpty()) {
+            return validateRequiredParams()
+        }
+
+        var tokenHandled: Boolean
+        while (iter.hasNext()) {
+            val token = iter.next()
+            tokenHandled = false
+
+            flags
+                .find { it.matches(token) }
+                ?.let {
+                    it.inner = true
+                    tokenHandled = true
+                }
+
+            if (tokenHandled) continue
+
+            params
+                .find { it.matches(token) }
+                ?.let {
+                    it.parseArgsFromIter(iter)
+                    tokenHandled = true
+                }
+
+            if (!tokenHandled) {
+                // Move the cursor position backwards since we've arrived at a token
+                // that we don't own
+                iter.previous()
+                break
+            }
+        }
+
+        return validateRequiredParams()
+    }
+
+    /**
+     * If [parse] or [parseAsSubCommand] does not produce a valid result, generate a list of errors
+     * based on missing elements
+     */
+    fun generateValidationErrorMessages(): List<String> {
+        val missingElements = mutableListOf<String>()
+
+        if (unhandledParams.isNotEmpty()) {
+            val names = unhandledParams.map { it.longName }
+            missingElements.add("No values passed for required params: $names")
+        }
+
+        if (unhandledSubCmds.isNotEmpty()) {
+            missingElements.addAll(unhandledSubCmds.map { it.longName })
+            val names = unhandledSubCmds.map { it.shortName }
+            missingElements.add("No values passed for required sub-commands: $names")
+        }
+
+        return missingElements
+    }
+
+    /** Check for any missing, required params, or any invalid subcommands */
+    private fun validateRequiredParams(): Boolean =
+        unhandledParams.isEmpty() && unhandledSubCmds.isEmpty() && unvalidatedSubCmds.isEmpty()
+
+    // If any required param (aka non-optional) hasn't handled a field, then return false
+    private val unhandledParams: List<Param>
+        get() = params.filter { (it is SingleArgParam<*>) && !it.handled }
+
+    private val unhandledSubCmds: List<SubCommand>
+        get() = subCommands.filter { (it is RequiredSubCommand<*> && !it.handled) }
+
+    private val unvalidatedSubCmds: List<SubCommand>
+        get() = subCommands.filter { !it.validationStatus }
+
+    private fun checkCliNames(short: String?, long: String): String? {
+        if (short != null && tokenSet.contains(short)) {
+            return short
+        }
+
+        if (tokenSet.contains(long)) {
+            return long
+        }
+
+        return null
+    }
+
+    private fun subCommandContainsSubCommands(cmd: ParseableCommand): Boolean =
+        cmd.parser.subCommands.isNotEmpty()
+
+    private fun registerNames(short: String?, long: String) {
+        if (short != null) {
+            tokenSet.add(short)
+        }
+        tokenSet.add(long)
+    }
+
+    /**
+     * Turns a [SingleArgParamOptional]<T> into a [SingleArgParam] by converting the [T?] into [T]
+     *
+     * @return a [SingleArgParam] property delegate
+     */
+    fun <T : Any> require(old: SingleArgParamOptional<T>): SingleArgParam<T> {
+        val newParam =
+            SingleArgParam(
+                longName = old.longName,
+                shortName = old.shortName,
+                description = old.description,
+                valueParser = old.valueParser,
+            )
+
+        replaceWithRequired(old, newParam)
+        return newParam
+    }
+
+    private fun <T : Any> replaceWithRequired(
+        old: SingleArgParamOptional<T>,
+        new: SingleArgParam<T>,
+    ) {
+        _params.remove(old)
+        _params.add(new)
+    }
+
+    /**
+     * Turns an [OptionalSubCommand] into a [RequiredSubCommand] by converting the [T?] in to [T]
+     *
+     * @return a [RequiredSubCommand] property delegate
+     */
+    fun <T : ParseableCommand> require(optional: OptionalSubCommand<T>): RequiredSubCommand<T> {
+        val newCmd = RequiredSubCommand(optional.cmd)
+        replaceWithRequired(optional, newCmd)
+        return newCmd
+    }
+
+    private fun <T : ParseableCommand> replaceWithRequired(
+        old: OptionalSubCommand<T>,
+        new: RequiredSubCommand<T>,
+    ) {
+        _subCommands.remove(old)
+        _subCommands.add(new)
+    }
+
+    internal fun flag(
+        longName: String,
+        shortName: String? = null,
+        description: String = "",
+    ): Flag {
+        checkCliNames(shortName, longName)?.let {
+            throw IllegalArgumentException("Detected reused flag name ($it)")
+        }
+        registerNames(shortName, longName)
+
+        val flag = Flag(shortName, longName, description)
+        _flags.add(flag)
+        return flag
+    }
+
+    internal fun <T : Any> param(
+        longName: String,
+        shortName: String? = null,
+        description: String = "",
+        valueParser: ValueParser<T>,
+    ): SingleArgParamOptional<T> {
+        checkCliNames(shortName, longName)?.let {
+            throw IllegalArgumentException("Detected reused param name ($it)")
+        }
+        registerNames(shortName, longName)
+
+        val param =
+            SingleArgParamOptional(
+                shortName = shortName,
+                longName = longName,
+                description = description,
+                valueParser = valueParser,
+            )
+        _params.add(param)
+        return param
+    }
+
+    internal fun <T : ParseableCommand> subCommand(
+        command: T,
+    ): OptionalSubCommand<T> {
+        checkCliNames(null, command.name)?.let {
+            throw IllegalArgumentException("Cannot re-use name for subcommand ($it)")
+        }
+
+        if (subCommandContainsSubCommands(command)) {
+            throw IllegalArgumentException(
+                "SubCommands may not contain other SubCommands. $command"
+            )
+        }
+
+        registerNames(null, command.name)
+
+        val subCmd = OptionalSubCommand(command)
+        _subCommands.add(subCmd)
+        return subCmd
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/commandline/Parameters.kt b/packages/SystemUI/src/com/android/systemui/statusbar/commandline/Parameters.kt
new file mode 100644
index 0000000..6ed5eed
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/commandline/Parameters.kt
@@ -0,0 +1,195 @@
+/*
+ * 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.commandline
+
+import android.util.IndentingPrintWriter
+import kotlin.properties.ReadOnlyProperty
+import kotlin.reflect.KProperty
+
+/**
+ * Definitions for all parameter types usable by [ParseableCommand]. Parameters are command line
+ * tokens that accept a fixed number of arguments and convert them to a parsed type.
+ *
+ * Example:
+ * ```
+ * my_command --single-arg-param arg
+ * ```
+ *
+ * In the example, `my_command` is the name of the command, `--single-arg-param` is the parameter,
+ * and `arg` is the value parsed by that parameter into its eventual type.
+ *
+ * Note on generics: The intended usage for parameters is to be able to return the parsed type from
+ * the given command as a `val` via property delegation. For example, let's say we have a command
+ * that has one optional and one required parameter:
+ * ```
+ * class MyCommand : ParseableCommand {
+ *   val requiredParam: Int by parser.param(...).required()
+ *   val optionalParam: Int? by parser.param(...)
+ * }
+ * ```
+ *
+ * In order to make the simple `param` method return the correct type, we need to do two things:
+ * 1. Break out the generic type into 2 pieces (TParsed and T)
+ * 2. Create two different underlying Parameter subclasses to handle the property delegation. One
+ *    handles `T?` and the other handles `T`. Note that in both cases, `TParsed` is always non-null
+ *    since the value parsed from the argument will throw an exception if missing or if it cannot be
+ *    parsed.
+ */
+
+/** A param type knows the number of arguments it expects */
+sealed interface Param : Describable {
+    val numArgs: Int
+
+    /**
+     * Consume [numArgs] items from the iterator and relay the result into its corresponding
+     * delegated type.
+     */
+    fun parseArgsFromIter(iterator: Iterator<String>)
+}
+
+/**
+ * Base class for required and optional SingleArgParam classes. For convenience, UnaryParam is
+ * defined as a [MultipleArgParam] where numArgs = 1. The benefit is that we can define the parsing
+ * in a single place, and yet on the client side we can unwrap the underlying list of params
+ * automatically.
+ */
+abstract class UnaryParamBase<out T, out TParsed : T>(val wrapped: MultipleArgParam<T, TParsed>) :
+    Param, ReadOnlyProperty<Any?, T> {
+    var handled = false
+
+    override fun describe(pw: IndentingPrintWriter) {
+        if (shortName != null) {
+            pw.print("$shortName, ")
+        }
+        pw.print(longName)
+        pw.println(" ${typeDescription()}")
+        if (description != null) {
+            pw.indented { pw.println(description) }
+        }
+    }
+
+    /**
+     * Try to describe the arg type. We can know if it's one of the base types what kind of input it
+     * takes. Otherwise just print "<arg>" and let the clients describe in the help text
+     */
+    private fun typeDescription() =
+        when (wrapped.valueParser) {
+            Type.Int -> "<int>"
+            Type.Float -> "<float>"
+            Type.String -> "<string>"
+            Type.Boolean -> "<boolean>"
+            else -> "<arg>"
+        }
+}
+
+/** Required single-arg parameter, delegating a non-null type to the client. */
+class SingleArgParam<out T : Any>(
+    override val longName: String,
+    override val shortName: String? = null,
+    override val description: String? = null,
+    val valueParser: ValueParser<T>,
+) :
+    UnaryParamBase<T, T>(
+        MultipleArgParam(
+            longName,
+            shortName,
+            1,
+            description,
+            valueParser,
+        )
+    ) {
+
+    override fun getValue(thisRef: Any?, property: KProperty<*>): T =
+        if (handled) {
+            wrapped.getValue(thisRef, property)[0]
+        } else {
+            throw IllegalStateException("Attempt to read property before parse() has executed")
+        }
+
+    override val numArgs: Int = 1
+
+    override fun parseArgsFromIter(iterator: Iterator<String>) {
+        wrapped.parseArgsFromIter(iterator)
+        handled = true
+    }
+}
+
+/** Optional single-argument parameter, delegating a nullable type to the client. */
+class SingleArgParamOptional<out T : Any>(
+    override val longName: String,
+    override val shortName: String? = null,
+    override val description: String? = null,
+    val valueParser: ValueParser<T>,
+) :
+    UnaryParamBase<T?, T>(
+        MultipleArgParam(
+            longName,
+            shortName,
+            1,
+            description,
+            valueParser,
+        )
+    ) {
+    override fun getValue(thisRef: Any?, property: KProperty<*>): T? =
+        wrapped.getValue(thisRef, property).getOrNull(0)
+
+    override val numArgs: Int = 1
+
+    override fun parseArgsFromIter(iterator: Iterator<String>) {
+        wrapped.parseArgsFromIter(iterator)
+        handled = true
+    }
+}
+
+/**
+ * Parses a list of args into the underlying [T] data type. The resultant value is an ordered list
+ * of type [TParsed].
+ *
+ * [T] and [TParsed] are split out here in the case where the entire param is optional. I.e., a
+ * MultipleArgParam<T?, T> indicates a command line argument that can be omitted. In that case, the
+ * inner list is List<T>?, NOT List<T?>. If the argument is provided, then the type is always going
+ * to be parsed into T rather than T?.
+ */
+class MultipleArgParam<out T, out TParsed : T>(
+    override val longName: String,
+    override val shortName: String? = null,
+    override val numArgs: Int = 1,
+    override val description: String? = null,
+    val valueParser: ValueParser<TParsed>,
+) : ReadOnlyProperty<Any?, List<TParsed>>, Param {
+    private val inner: MutableList<TParsed> = mutableListOf()
+
+    override fun getValue(thisRef: Any?, property: KProperty<*>): List<TParsed> = inner
+
+    /**
+     * Consumes [numArgs] values of the iterator and parses them into [TParsed].
+     *
+     * @throws ArgParseError on the first failure
+     */
+    override fun parseArgsFromIter(iterator: Iterator<String>) {
+        if (!iterator.hasNext()) {
+            throw ArgParseError("no argument provided for $shortName")
+        }
+        for (i in 0 until numArgs) {
+            valueParser
+                .parseValue(iterator.next())
+                .fold(onSuccess = { inner.add(it) }, onFailure = { throw it })
+        }
+    }
+}
+
+data class ArgParseError(override val message: String) : Exception(message)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/commandline/ParseableCommand.kt b/packages/SystemUI/src/com/android/systemui/statusbar/commandline/ParseableCommand.kt
new file mode 100644
index 0000000..ecd3fa6
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/commandline/ParseableCommand.kt
@@ -0,0 +1,395 @@
+/*
+ * 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.commandline
+
+import android.util.IndentingPrintWriter
+import java.io.PrintWriter
+import java.lang.IllegalArgumentException
+import kotlin.properties.ReadOnlyProperty
+import kotlin.reflect.KProperty
+
+/**
+ * An implementation of [Command] that includes a [CommandParser] which can set all delegated
+ * properties.
+ *
+ * As the number of registrants to [CommandRegistry] grows, we should have a default mechanism for
+ * parsing common command line arguments. We are not expecting to build an arbitrarily-functional
+ * CLI, nor a GNU arg parse compliant interface here, we simply want to be able to empower clients
+ * to create simple CLI grammars such as:
+ * ```
+ * $ my_command [-f|--flag]
+ * $ my_command [-a|--arg] <params...>
+ * $ my_command [subcommand1] [subcommand2]
+ * $ my_command <positional_arg ...> # not-yet implemented
+ * ```
+ *
+ * Note that the flags `-h` and `--help` are reserved for the base class. It seems prudent to just
+ * avoid them in your implementation.
+ *
+ * Usage:
+ *
+ * The intended usage tries to be clever enough to enable good ergonomics, while not too clever as
+ * to be unmaintainable. Using the default parser is done using property delegates, and looks like:
+ * ```
+ * class MyCommand(
+ *     onExecute: (cmd: MyCommand, pw: PrintWriter) -> ()
+ * ) : ParseableCommand(name) {
+ *     val flag1 by flag(
+ *         shortName = "-f",
+ *         longName = "--flag",
+ *         required = false,
+ *     )
+ *     val param1: String by param(
+ *         shortName = "-a",
+ *         longName = "--args",
+ *         valueParser = Type.String
+ *     ).required()
+ *     val param2: Int by param(..., valueParser = Type.Int)
+ *     val subCommand by subCommand(...)
+ *
+ *     override fun execute(pw: PrintWriter) {
+ *         onExecute(this, pw)
+ *     }
+ *
+ *     companion object {
+ *        const val name = "my_command"
+ *     }
+ * }
+ *
+ * fun main() {
+ *     fun printArgs(cmd: MyCommand, pw: PrintWriter) {
+ *         pw.println("${cmd.flag1}")
+ *         pw.println("${cmd.param1}")
+ *         pw.println("${cmd.param2}")
+ *         pw.println("${cmd.subCommand}")
+ *     }
+ *
+ *     commandRegistry.registerCommand(MyCommand.companion.name) {
+ *         MyCommand() { (cmd, pw) ->
+ *             printArgs(cmd, pw)
+ *         }
+ *     }
+ * }
+ *
+ * ```
+ */
+abstract class ParseableCommand(val name: String, val description: String? = null) : Command {
+    val parser: CommandParser = CommandParser()
+
+    val help by flag(longName = "help", shortName = "h", description = "Print help and return")
+
+    /**
+     * After [execute(pw, args)] is called, this class goes through a parsing stage and sets all
+     * delegated properties. It is safe to read any delegated properties here.
+     *
+     * This method is never called for [SubCommand]s, since they are associated with a top-level
+     * command that handles [execute]
+     */
+    abstract fun execute(pw: PrintWriter)
+
+    /**
+     * Given a command string list, [execute] parses the incoming command and validates the input.
+     * If this command or any of its subcommands is passed `-h` or `--help`, then execute will only
+     * print the relevant help message and exit.
+     *
+     * If any error is thrown during parsing, we will catch and log the error. This process should
+     * _never_ take down its process. Override [onParseFailed] to handle an [ArgParseError].
+     *
+     * Important: none of the delegated fields can be read before this stage.
+     */
+    override fun execute(pw: PrintWriter, args: List<String>) {
+        val success: Boolean
+        try {
+            success = parser.parse(args)
+        } catch (e: ArgParseError) {
+            pw.println(e.message)
+            onParseFailed(e)
+            return
+        } catch (e: Exception) {
+            pw.println("Unknown exception encountered during parse")
+            pw.println(e)
+            return
+        }
+
+        // Now we've parsed the incoming command without error. There are two things to check:
+        // 1. If any help is requested, print the help message and return
+        // 2. Otherwise, make sure required params have been passed in, and execute
+
+        val helpSubCmds = subCmdsRequestingHelp()
+
+        // Top-level help encapsulates subcommands. Otherwise, if _any_ subcommand requests
+        // help then defer to them. Else, just execute
+        if (help) {
+            help(pw)
+        } else if (helpSubCmds.isNotEmpty()) {
+            helpSubCmds.forEach { it.help(pw) }
+        } else {
+            if (!success) {
+                parser.generateValidationErrorMessages().forEach { pw.println(it) }
+            } else {
+                execute(pw)
+            }
+        }
+    }
+
+    /**
+     * Returns a list of all commands that asked for help. If non-empty, parsing will stop to print
+     * help. It is not guaranteed that delegates are fulfilled if help is requested
+     */
+    private fun subCmdsRequestingHelp(): List<ParseableCommand> =
+        parser.subCommands.filter { it.cmd.help }.map { it.cmd }
+
+    /** Override to do something when parsing fails */
+    open fun onParseFailed(error: ArgParseError) {}
+
+    /** Override to print a usage clause. E.g. `usage: my-cmd <arg1> <arg2>` */
+    open fun usage(pw: IndentingPrintWriter) {}
+
+    /**
+     * Print out the list of tokens, their received types if any, and their description in a
+     * formatted string.
+     *
+     * Example:
+     * ```
+     * my-command:
+     *   MyCmd.description
+     *
+     * [optional] usage block
+     *
+     * Flags:
+     *   -f
+     *     description
+     *   --flag2
+     *     description
+     *
+     * Parameters:
+     *   Required:
+     *     -p1 [Param.Type]
+     *       description
+     *     --param2 [Param.Type]
+     *       description
+     *   Optional:
+     *     same as above
+     *
+     * SubCommands:
+     *   Required:
+     *     ...
+     *   Optional:
+     *     ...
+     * ```
+     */
+    override fun help(pw: PrintWriter) {
+        val ipw = IndentingPrintWriter(pw)
+        ipw.printBoxed(name)
+        ipw.println()
+
+        // Allow for a simple `usage` block for clients
+        ipw.indented { usage(ipw) }
+
+        if (description != null) {
+            ipw.indented { ipw.println(description) }
+            ipw.println()
+        }
+
+        val flags = parser.flags
+        if (flags.isNotEmpty()) {
+            ipw.println("FLAGS:")
+            ipw.indented {
+                flags.forEach {
+                    it.describe(ipw)
+                    ipw.println()
+                }
+            }
+        }
+
+        val (required, optional) = parser.params.partition { it is SingleArgParam<*> }
+        if (required.isNotEmpty()) {
+            ipw.println("REQUIRED PARAMS:")
+            required.describe(ipw)
+        }
+        if (optional.isNotEmpty()) {
+            ipw.println("OPTIONAL PARAMS:")
+            optional.describe(ipw)
+        }
+
+        val (reqSub, optSub) = parser.subCommands.partition { it is RequiredSubCommand<*> }
+        if (reqSub.isNotEmpty()) {
+            ipw.println("REQUIRED SUBCOMMANDS:")
+            reqSub.describe(ipw)
+        }
+        if (optSub.isNotEmpty()) {
+            ipw.println("OPTIONAL SUBCOMMANDS:")
+            optSub.describe(ipw)
+        }
+    }
+
+    fun flag(
+        longName: String,
+        shortName: String? = null,
+        description: String = "",
+    ): Flag {
+        if (!checkShortName(shortName)) {
+            throw IllegalArgumentException(
+                "Flag short name must be one character long, or null. Got ($shortName)"
+            )
+        }
+
+        if (!checkLongName(longName)) {
+            throw IllegalArgumentException("Flags must not start with '-'. Got $($longName)")
+        }
+
+        val short = shortName?.let { "-$shortName" }
+        val long = "--$longName"
+
+        return parser.flag(long, short, description)
+    }
+
+    fun <T : Any> param(
+        longName: String,
+        shortName: String? = null,
+        description: String = "",
+        valueParser: ValueParser<T>,
+    ): SingleArgParamOptional<T> {
+        if (!checkShortName(shortName)) {
+            throw IllegalArgumentException(
+                "Parameter short name must be one character long, or null. Got ($shortName)"
+            )
+        }
+
+        if (!checkLongName(longName)) {
+            throw IllegalArgumentException("Parameters must not start with '-'. Got $($longName)")
+        }
+
+        val short = shortName?.let { "-$shortName" }
+        val long = "--$longName"
+
+        return parser.param(long, short, description, valueParser)
+    }
+
+    fun <T : ParseableCommand> subCommand(
+        command: T,
+    ) = parser.subCommand(command)
+
+    /** For use in conjunction with [param], makes the parameter required */
+    fun <T : Any> SingleArgParamOptional<T>.required(): SingleArgParam<T> = parser.require(this)
+
+    /** For use in conjunction with [subCommand], makes the given [SubCommand] required */
+    fun <T : ParseableCommand> OptionalSubCommand<T>.required(): RequiredSubCommand<T> =
+        parser.require(this)
+
+    private fun checkShortName(short: String?): Boolean {
+        return short == null || short.length == 1
+    }
+
+    private fun checkLongName(long: String): Boolean {
+        return !long.startsWith("-")
+    }
+
+    companion object {
+        fun Iterable<Describable>.describe(pw: IndentingPrintWriter) {
+            pw.indented {
+                forEach {
+                    it.describe(pw)
+                    pw.println()
+                }
+            }
+        }
+    }
+}
+
+/**
+ * A flag is a boolean value passed over the command line. It can have a short form or long form.
+ * The value is [Boolean.true] if the flag is found, else false
+ */
+data class Flag(
+    override val shortName: String? = null,
+    override val longName: String,
+    override val description: String? = null,
+) : ReadOnlyProperty<Any?, Boolean>, Describable {
+    var inner: Boolean = false
+
+    override fun getValue(thisRef: Any?, property: KProperty<*>) = inner
+}
+
+/**
+ * Named CLI token. Can have a short or long name. Note: consider renaming to "primary" and
+ * "secondary" names since we don't actually care what the strings are
+ *
+ * Flags and params will have [shortName]s that are always prefixed with a single dash, while
+ * [longName]s are prefixed by a double dash. E.g., `my_command -f --flag`.
+ *
+ * Subcommands do not do any prefixing, and register their name as the [longName]
+ *
+ * Can be matched against an incoming token
+ */
+interface CliNamed {
+    val shortName: String?
+    val longName: String
+
+    fun matches(token: String) = shortName == token || longName == token
+}
+
+interface Describable : CliNamed {
+    val description: String?
+
+    fun describe(pw: IndentingPrintWriter) {
+        if (shortName != null) {
+            pw.print("$shortName, ")
+        }
+        pw.print(longName)
+        pw.println()
+        if (description != null) {
+            pw.indented { pw.println(description) }
+        }
+    }
+}
+
+/**
+ * Print [s] inside of a unicode character box, like so:
+ * ```
+ *  ╔═══════════╗
+ *  ║ my-string ║
+ *  ╚═══════════╝
+ * ```
+ */
+fun PrintWriter.printDoubleBoxed(s: String) {
+    val length = s.length
+    println("╔${"═".repeat(length + 2)}╗")
+    println("║ $s ║")
+    println("╚${"═".repeat(length + 2)}╝")
+}
+
+/**
+ * Print [s] inside of a unicode character box, like so:
+ * ```
+ *  ┌───────────┐
+ *  │ my-string │
+ *  └───────────┘
+ * ```
+ */
+fun PrintWriter.printBoxed(s: String) {
+    val length = s.length
+    println("┌${"─".repeat(length + 2)}┐")
+    println("│ $s │")
+    println("└${"─".repeat(length + 2)}┘")
+}
+
+fun IndentingPrintWriter.indented(block: () -> Unit) {
+    increaseIndent()
+    block()
+    decreaseIndent()
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/commandline/SubCommand.kt b/packages/SystemUI/src/com/android/systemui/statusbar/commandline/SubCommand.kt
new file mode 100644
index 0000000..41bac86
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/commandline/SubCommand.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.commandline
+
+import android.util.IndentingPrintWriter
+import kotlin.properties.ReadOnlyProperty
+import kotlin.reflect.KProperty
+
+/**
+ * Sub commands wrap [ParseableCommand]s and are attached to a parent [ParseableCommand]. As such
+ * they have their own parser which will parse the args as a subcommand. I.e., the subcommand's
+ * parser will consume the iterator created by the parent, reversing the index when it reaches an
+ * unknown token.
+ *
+ * In order to keep subcommands relatively simple and not have to do complicated validation, sub
+ * commands will return control to the parent parser as soon as they discover a token that they do
+ * not own. They will throw an [ArgParseError] if parsing fails or if they don't receive arguments
+ * for a required parameter.
+ */
+sealed interface SubCommand : Describable {
+    val cmd: ParseableCommand
+
+    /** Checks if all of the required elements were passed in to [parseSubCommandArgs] */
+    var validationStatus: Boolean
+
+    /**
+     * To keep parsing simple, [parseSubCommandArgs] requires a [ListIterator] so that it can rewind
+     * the iterator when it yields control upwards
+     */
+    fun parseSubCommandArgs(iterator: ListIterator<String>)
+}
+
+/**
+ * Note that the delegated type from the subcommand is `T: ParseableCommand?`. SubCommands are
+ * created via adding a fully-formed [ParseableCommand] to parent command.
+ *
+ * At this point in time, I don't recommend nesting subcommands.
+ */
+class OptionalSubCommand<T : ParseableCommand>(
+    override val cmd: T,
+) : SubCommand, ReadOnlyProperty<Any?, ParseableCommand?> {
+    override val shortName: String? = null
+    override val longName: String = cmd.name
+    override val description: String? = cmd.description
+    override var validationStatus = true
+
+    private var isPresent = false
+
+    /** Consume tokens from the iterator and pass them to the wrapped command */
+    override fun parseSubCommandArgs(iterator: ListIterator<String>) {
+        validationStatus = cmd.parser.parseAsSubCommand(iterator)
+        isPresent = true
+    }
+
+    override fun getValue(thisRef: Any?, property: KProperty<*>): T? =
+        if (isPresent) {
+            cmd
+        } else {
+            null
+        }
+
+    override fun describe(pw: IndentingPrintWriter) {
+        cmd.help(pw)
+    }
+}
+
+/**
+ * Non-optional subcommand impl. Top-level parser is expected to throw [ArgParseError] if this token
+ * is not present in the incoming command
+ */
+class RequiredSubCommand<T : ParseableCommand>(
+    override val cmd: T,
+) : SubCommand, ReadOnlyProperty<Any?, ParseableCommand> {
+    override val shortName: String? = null
+    override val longName: String = cmd.name
+    override val description: String? = cmd.description
+    override var validationStatus = true
+
+    /** Unhandled, required subcommands are an error */
+    var handled = false
+
+    override fun parseSubCommandArgs(iterator: ListIterator<String>) {
+        validationStatus = cmd.parser.parseAsSubCommand(iterator)
+        handled = true
+    }
+
+    override fun getValue(thisRef: Any?, property: KProperty<*>): ParseableCommand = cmd
+
+    override fun describe(pw: IndentingPrintWriter) {
+        cmd.help(pw)
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/commandline/ValueParser.kt b/packages/SystemUI/src/com/android/systemui/statusbar/commandline/ValueParser.kt
new file mode 100644
index 0000000..01083d9
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/commandline/ValueParser.kt
@@ -0,0 +1,173 @@
+/*
+ * 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.commandline
+
+import kotlin.contracts.ExperimentalContracts
+import kotlin.contracts.InvocationKind
+import kotlin.contracts.contract
+
+/**
+ * Utilities for parsing the [String] command line arguments. Arguments are related to the
+ * [Parameter] type, which declares the number of, and resulting type of, the arguments that it
+ * takes when parsing. For Example:
+ * ```
+ * my-command --param <str> --param2 <int>
+ * ```
+ *
+ * Defines 2 parameters, the first of which takes a string, and the second requires an int. Because
+ * fundamentally _everything_ is a string, we have to define a convenient way to get from the
+ * incoming `StringArg` to the resulting `T`-arg, where `T` is the type required by the client.
+ *
+ * Parsing is therefore a relatively straightforward operation: (String) -> T. However, since
+ * parsing can always fail, the type is actually (String) -> Result<T>. We will always want to fail
+ * on the first error and propagate it to the caller (typically this results in printing the `help`
+ * message of the command`).
+ *
+ * The identity parsing is trivial:
+ * ```
+ * (s: String) -> String = { s -> s }
+ * ```
+ *
+ * Basic mappings are actually even provided by Kotlin's stdlib:
+ * ```
+ * (s: String) -> Boolean = { s -> s.toBooleanOrNull() }
+ * (s: String) -> Int = { s -> s.toIntOrNull() }
+ * ...
+ * ```
+ *
+ * In order to properly encode errors, we will ascribe an error type to any `null` values, such that
+ * parsing looks like this:
+ * ```
+ * val mapping: (String) -> T? = {...} // for some T
+ * val parser: (String) -> Result<T> = { s ->
+ *   mapping(s)?.let {
+ *     Result.success(it)
+ *   } ?: Result.failure(/* some failure type */)
+ * }
+ * ```
+ *
+ * Composition
+ *
+ * The ability to compose value parsing enables us to provide a couple of reasonable default parsers
+ * and allow clients to seamlessly build upon that using map functions. Consider the case where we
+ * want to validate that a value is an [Int] between 0 and 100. We start with the generic [Int]
+ * parser, and a validator, of the type (Int) -> Result<Int>:
+ * ```
+ * val intParser = { s ->
+ *   s.toStringOrNull().?let {...} ?: ...
+ * }
+ *
+ * val validator = { i ->
+ *   if (i > 100 || i < 0) {
+ *     Result.failure(...)
+ *   } else {
+ *     Result.success(i)
+ *   }
+ * ```
+ *
+ * In order to combine these functions, we need to define a new [flatMap] function that can get us
+ * from a `Result<T>` to a `Result<R>`, and short-circuit on any error. We want to see this:
+ * ```
+ * val validatingParser = { s ->
+ *   intParser.invoke(s).flatMap { i ->
+ *     validator(i)
+ *   }
+ * }
+ * ```
+ *
+ * The flatMap is relatively simply defined, we can mimic the existing definition for [Result.map],
+ * though the implementation is uglier because of the `internal` definition for `value`
+ *
+ * ```
+ * inline fun <R, T> Result<T>.flatMap(transform: (value: T) -> Result<R>): Result<R> {
+ *   return when {
+ *     isSuccess -> transform(getOrThrow())
+ *     else -> Result.failure(exceptionOrNull()!!)
+ *   }
+ * }
+ * ```
+ */
+
+/**
+ * Given a [transform] that returns a [Result], apply the transform to this result, unwrapping the
+ * return value so that
+ *
+ * These [contract] and [callsInPlace] methods are copied from the [Result.map] definition
+ */
+@OptIn(ExperimentalContracts::class)
+inline fun <R, T> Result<T>.flatMap(transform: (value: T) -> Result<R>): Result<R> {
+    contract { callsInPlace(transform, InvocationKind.AT_MOST_ONCE) }
+
+    return when {
+        // Should never throw, we just don't have access to [this.value]
+        isSuccess -> transform(getOrThrow())
+        // Exception should never be null here
+        else -> Result.failure(exceptionOrNull()!!)
+    }
+}
+
+/**
+ * ValueParser turns a [String] into a Result<A> by applying a transform. See the default
+ * implementations below for starting points. The intention here is to provide the base mappings and
+ * allow clients to attach their own transforms. They are expected to succeed or return null on
+ * failure. The failure is propagated to the command parser as a Result and will fail on any
+ * [Result.failure]
+ */
+fun interface ValueParser<out A> {
+    fun parseValue(value: String): Result<A>
+}
+
+/** Map a [ValueParser] of type A to one of type B, by applying the given [transform] */
+inline fun <A, B> ValueParser<A>.map(crossinline transform: (A) -> B?): ValueParser<B> {
+    return ValueParser<B> { value ->
+        this.parseValue(value).flatMap { a ->
+            transform(a)?.let { b -> Result.success(b) }
+                ?: Result.failure(ArgParseError("Failed to transform value $value"))
+        }
+    }
+}
+
+/**
+ * Base type parsers are provided by the lib, and can be simply composed upon by [ValueParser.map]
+ * functions on the parser
+ */
+
+/** String parsing always succeeds if the value exists */
+private val parseString: ValueParser<String> = ValueParser { value -> Result.success(value) }
+
+private val parseBoolean: ValueParser<Boolean> = ValueParser { value ->
+    value.toBooleanStrictOrNull()?.let { Result.success(it) }
+        ?: Result.failure(ArgParseError("Failed to parse $value as a boolean"))
+}
+
+private val parseInt: ValueParser<Int> = ValueParser { value ->
+    value.toIntOrNull()?.let { Result.success(it) }
+        ?: Result.failure(ArgParseError("Failed to parse $value as an int"))
+}
+
+private val parseFloat: ValueParser<Float> = ValueParser { value ->
+    value.toFloatOrNull()?.let { Result.success(it) }
+        ?: Result.failure(ArgParseError("Failed to parse $value as a float"))
+}
+
+/** Default parsers that can be use as-is, or [map]ped to another type */
+object Type {
+    val Boolean = parseBoolean
+    val Int = parseInt
+    val Float = parseFloat
+    val String = parseString
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt
index 8778463..11b1053 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt
@@ -124,6 +124,7 @@
     private var showSensitiveContentForCurrentUser = false
     private var showSensitiveContentForManagedUser = false
     private var managedUserHandle: UserHandle? = null
+    private var mSplitShadeEnabled = false
 
     // TODO(b/202758428): refactor so that we can test color updates via region samping, similar to
     //  how we test color updates when theme changes (See testThemeChangeUpdatesTextColor).
@@ -131,6 +132,7 @@
     // TODO: Move logic into SmartspaceView
     var stateChangeListener = object : View.OnAttachStateChangeListener {
         override fun onViewAttachedToWindow(v: View) {
+            (v as SmartspaceView).setSplitShadeEnabled(mSplitShadeEnabled)
             smartspaceViews.add(v as SmartspaceView)
 
             connectSession()
@@ -221,6 +223,11 @@
             execution.assertIsMainThread()
             smartspaceViews.forEach { it.setDozeAmount(eased) }
         }
+
+        override fun onDozingChanged(isDozing: Boolean) {
+            execution.assertIsMainThread()
+            smartspaceViews.forEach { it.setDozing(isDozing) }
+        }
     }
 
     private val deviceProvisionedListener =
@@ -426,6 +433,11 @@
         reloadSmartspace()
     }
 
+    fun setSplitShadeEnabled(enabled: Boolean) {
+        mSplitShadeEnabled = enabled
+        smartspaceViews.forEach { it.setSplitShadeEnabled(enabled) }
+    }
+
     /**
      * Requests the smartspace session for an update.
      */
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinator.kt
index 2fa070c..07eb8a00 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinator.kt
@@ -28,12 +28,9 @@
 import com.android.systemui.keyguard.data.repository.KeyguardRepository
 import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
 import com.android.systemui.keyguard.shared.model.KeyguardState
-import com.android.systemui.keyguard.shared.model.TransitionState
-import com.android.systemui.keyguard.shared.model.TransitionStep
 import com.android.systemui.plugins.statusbar.StatusBarStateController
 import com.android.systemui.statusbar.StatusBarState
 import com.android.systemui.statusbar.expansionChanges
-import com.android.systemui.statusbar.notification.NotifPipelineFlags
 import com.android.systemui.statusbar.notification.collection.NotifPipeline
 import com.android.systemui.statusbar.notification.collection.NotificationEntry
 import com.android.systemui.statusbar.notification.collection.coordinator.dagger.CoordinatorScope
@@ -50,30 +47,29 @@
 import com.android.systemui.util.settings.SettingsProxyExt.observerFlow
 import java.io.PrintWriter
 import javax.inject.Inject
-import kotlin.time.Duration
 import kotlin.time.Duration.Companion.seconds
 import kotlinx.coroutines.CoroutineDispatcher
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.Job
 import kotlinx.coroutines.coroutineScope
 import kotlinx.coroutines.delay
 import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableSharedFlow
 import kotlinx.coroutines.flow.collectLatest
 import kotlinx.coroutines.flow.conflate
 import kotlinx.coroutines.flow.distinctUntilChanged
-import kotlinx.coroutines.flow.emitAll
-import kotlinx.coroutines.flow.first
 import kotlinx.coroutines.flow.flowOn
 import kotlinx.coroutines.flow.map
 import kotlinx.coroutines.flow.onEach
 import kotlinx.coroutines.flow.onStart
-import kotlinx.coroutines.flow.transformLatest
 import kotlinx.coroutines.launch
 import kotlinx.coroutines.yield
 
 /**
  * Filters low priority and privacy-sensitive notifications from the lockscreen, and hides section
- * headers on the lockscreen.
+ * headers on the lockscreen. If enabled, it will also track and hide seen notifications on the
+ * lockscreen.
  */
 @CoordinatorScope
 class KeyguardCoordinator
@@ -86,7 +82,6 @@
     private val keyguardRepository: KeyguardRepository,
     private val keyguardTransitionRepository: KeyguardTransitionRepository,
     private val logger: KeyguardCoordinatorLogger,
-    private val notifPipelineFlags: NotifPipelineFlags,
     @Application private val scope: CoroutineScope,
     private val sectionHeaderVisibilityProvider: SectionHeaderVisibilityProvider,
     private val secureSettings: SecureSettings,
@@ -95,6 +90,8 @@
 ) : Coordinator, Dumpable {
 
     private val unseenNotifications = mutableSetOf<NotificationEntry>()
+    private val unseenEntryAdded = MutableSharedFlow<NotificationEntry>(extraBufferCapacity = 1)
+    private val unseenEntryRemoved = MutableSharedFlow<NotificationEntry>(extraBufferCapacity = 1)
     private var unseenFilterEnabled = false
 
     override fun attach(pipeline: NotifPipeline) {
@@ -109,79 +106,130 @@
     private fun attachUnseenFilter(pipeline: NotifPipeline) {
         pipeline.addFinalizeFilter(unseenNotifFilter)
         pipeline.addCollectionListener(collectionListener)
-        scope.launch { trackUnseenNotificationsWhileUnlocked() }
-        scope.launch { invalidateWhenUnseenSettingChanges() }
+        scope.launch { trackUnseenFilterSettingChanges() }
         dumpManager.registerDumpable(this)
     }
 
-    private suspend fun trackUnseenNotificationsWhileUnlocked() {
-        // Whether or not we're actively tracking unseen notifications to mark them as seen when
-        // appropriate.
-        val isTrackingUnseen: Flow<Boolean> =
-            keyguardRepository.isKeyguardShowing
-                // transformLatest so that we can cancel listening to keyguard transitions once
-                // isKeyguardShowing changes (after a successful transition to the keyguard).
-                .transformLatest { isShowing ->
-                    if (isShowing) {
-                        // If the keyguard is showing, we're not tracking unseen.
-                        emit(false)
-                    } else {
-                        // If the keyguard stops showing, then start tracking unseen notifications.
-                        emit(true)
-                        // If the screen is turning off, stop tracking, but if that transition is
-                        // cancelled, then start again.
-                        emitAll(
-                            keyguardTransitionRepository.transitions.map { step ->
-                                !step.isScreenTurningOff
-                            }
-                        )
-                    }
-                }
-                // Prevent double emit of `false` caused by transition to AOD, followed by keyguard
-                // showing
+    private suspend fun trackSeenNotifications() {
+        // Whether or not keyguard is visible (or occluded).
+        val isKeyguardPresent: Flow<Boolean> =
+            keyguardTransitionRepository.transitions
+                .map { step -> step.to != KeyguardState.GONE }
                 .distinctUntilChanged()
                 .onEach { trackingUnseen -> logger.logTrackingUnseen(trackingUnseen) }
 
-        // Use collectLatest so that trackUnseenNotifications() is cancelled when the keyguard is
-        // showing again
-        var clearUnseenOnBeginTracking = false
-        isTrackingUnseen.collectLatest { trackingUnseen ->
-            if (!trackingUnseen) {
-                // Wait for the user to spend enough time on the lock screen before clearing unseen
-                // set when unlocked
-                awaitTimeSpentNotDozing(SEEN_TIMEOUT)
-                clearUnseenOnBeginTracking = true
-                logger.logSeenOnLockscreen()
+        // Separately track seen notifications while the device is locked, applying once the device
+        // is unlocked.
+        val notificationsSeenWhileLocked = mutableSetOf<NotificationEntry>()
+
+        // Use [collectLatest] to cancel any running jobs when [trackingUnseen] changes.
+        isKeyguardPresent.collectLatest { isKeyguardPresent: Boolean ->
+            if (isKeyguardPresent) {
+                // Keyguard is not gone, notifications need to be visible for a certain threshold
+                // before being marked as seen
+                trackSeenNotificationsWhileLocked(notificationsSeenWhileLocked)
             } else {
-                if (clearUnseenOnBeginTracking) {
-                    clearUnseenOnBeginTracking = false
-                    logger.logAllMarkedSeenOnUnlock()
-                    unseenNotifications.clear()
+                // Mark all seen-while-locked notifications as seen for real.
+                if (notificationsSeenWhileLocked.isNotEmpty()) {
+                    unseenNotifications.removeAll(notificationsSeenWhileLocked)
+                    logger.logAllMarkedSeenOnUnlock(
+                        seenCount = notificationsSeenWhileLocked.size,
+                        remainingUnseenCount = unseenNotifications.size
+                    )
+                    notificationsSeenWhileLocked.clear()
                 }
                 unseenNotifFilter.invalidateList("keyguard no longer showing")
-                trackUnseenNotifications()
+                // Keyguard is gone, notifications can be immediately marked as seen when they
+                // become visible.
+                trackSeenNotificationsWhileUnlocked()
             }
         }
     }
 
-    private suspend fun awaitTimeSpentNotDozing(duration: Duration) {
-        keyguardRepository.isDozing
-            // Use transformLatest so that the timeout delay is cancelled if the device enters doze,
-            // and is restarted when doze ends.
-            .transformLatest { isDozing ->
-                if (!isDozing) {
-                    delay(duration)
-                    // Signal timeout has completed
-                    emit(Unit)
+    /**
+     * Keep [notificationsSeenWhileLocked] updated to represent which notifications have actually
+     * been "seen" while the device is on the keyguard.
+     */
+    private suspend fun trackSeenNotificationsWhileLocked(
+        notificationsSeenWhileLocked: MutableSet<NotificationEntry>,
+    ) = coroutineScope {
+        // Remove removed notifications from the set
+        launch {
+            unseenEntryRemoved.collect { entry ->
+                if (notificationsSeenWhileLocked.remove(entry)) {
+                    logger.logRemoveSeenOnLockscreen(entry)
                 }
             }
-            // Suspend until the first emission
-            .first()
+        }
+        // Use collectLatest so that the timeout delay is cancelled if the device enters doze, and
+        // is restarted when doze ends.
+        keyguardRepository.isDozing.collectLatest { isDozing ->
+            if (!isDozing) {
+                trackSeenNotificationsWhileLockedAndNotDozing(notificationsSeenWhileLocked)
+            }
+        }
     }
 
-    // Track "unseen" notifications, marking them as seen when either shade is expanded or the
+    /**
+     * Keep [notificationsSeenWhileLocked] updated to represent which notifications have actually
+     * been "seen" while the device is on the keyguard and not dozing. Any new and existing unseen
+     * notifications are not marked as seen until they are visible for the [SEEN_TIMEOUT] duration.
+     */
+    private suspend fun trackSeenNotificationsWhileLockedAndNotDozing(
+        notificationsSeenWhileLocked: MutableSet<NotificationEntry>
+    ) = coroutineScope {
+        // All child tracking jobs will be cancelled automatically when this is cancelled.
+        val trackingJobsByEntry = mutableMapOf<NotificationEntry, Job>()
+
+        /**
+         * Wait for the user to spend enough time on the lock screen before removing notification
+         * from unseen set upon unlock.
+         */
+        suspend fun trackSeenDurationThreshold(entry: NotificationEntry) {
+            if (notificationsSeenWhileLocked.remove(entry)) {
+                logger.logResetSeenOnLockscreen(entry)
+            }
+            delay(SEEN_TIMEOUT)
+            notificationsSeenWhileLocked.add(entry)
+            trackingJobsByEntry.remove(entry)
+            logger.logSeenOnLockscreen(entry)
+        }
+
+        /** Stop any unseen tracking when a notification is removed. */
+        suspend fun stopTrackingRemovedNotifs(): Nothing =
+            unseenEntryRemoved.collect { entry ->
+                trackingJobsByEntry.remove(entry)?.let {
+                    it.cancel()
+                    logger.logStopTrackingLockscreenSeenDuration(entry)
+                }
+            }
+
+        /** Start tracking new notifications when they are posted. */
+        suspend fun trackNewUnseenNotifs(): Nothing = coroutineScope {
+            unseenEntryAdded.collect { entry ->
+                logger.logTrackingLockscreenSeenDuration(entry)
+                // If this is an update, reset the tracking.
+                trackingJobsByEntry[entry]?.let {
+                    it.cancel()
+                    logger.logResetSeenOnLockscreen(entry)
+                }
+                trackingJobsByEntry[entry] = launch { trackSeenDurationThreshold(entry) }
+            }
+        }
+
+        // Start tracking for all notifications that are currently unseen.
+        logger.logTrackingLockscreenSeenDuration(unseenNotifications)
+        unseenNotifications.forEach { entry ->
+            trackingJobsByEntry[entry] = launch { trackSeenDurationThreshold(entry) }
+        }
+
+        launch { trackNewUnseenNotifs() }
+        launch { stopTrackingRemovedNotifs() }
+    }
+
+    // Track "seen" notifications, marking them as such when either shade is expanded or the
     // notification becomes heads up.
-    private suspend fun trackUnseenNotifications() {
+    private suspend fun trackSeenNotificationsWhileUnlocked() {
         coroutineScope {
             launch { clearUnseenNotificationsWhenShadeIsExpanded() }
             launch { markHeadsUpNotificationsAsSeen() }
@@ -212,7 +260,7 @@
         }
     }
 
-    private suspend fun invalidateWhenUnseenSettingChanges() {
+    private suspend fun trackUnseenFilterSettingChanges() {
         secureSettings
             // emit whenever the setting has changed
             .observerFlow(
@@ -228,17 +276,23 @@
                     UserHandle.USER_CURRENT,
                 ) == 1
             }
+            // don't emit anything if nothing has changed
+            .distinctUntilChanged()
             // perform lookups on the bg thread pool
             .flowOn(bgDispatcher)
             // only track the most recent emission, if events are happening faster than they can be
             // consumed
             .conflate()
-            // update local field and invalidate if necessary
-            .collect { setting ->
+            .collectLatest { setting ->
+                // update local field and invalidate if necessary
                 if (setting != unseenFilterEnabled) {
                     unseenFilterEnabled = setting
                     unseenNotifFilter.invalidateList("unseen setting changed")
                 }
+                // if the setting is enabled, then start tracking and filtering unseen notifications
+                if (setting) {
+                    trackSeenNotifications()
+                }
             }
     }
 
@@ -250,6 +304,7 @@
                 ) {
                     logger.logUnseenAdded(entry.key)
                     unseenNotifications.add(entry)
+                    unseenEntryAdded.tryEmit(entry)
                 }
             }
 
@@ -259,12 +314,14 @@
                 ) {
                     logger.logUnseenUpdated(entry.key)
                     unseenNotifications.add(entry)
+                    unseenEntryAdded.tryEmit(entry)
                 }
             }
 
             override fun onEntryRemoved(entry: NotificationEntry, reason: Int) {
                 if (unseenNotifications.remove(entry)) {
                     logger.logUnseenRemoved(entry.key)
+                    unseenEntryRemoved.tryEmit(entry)
                 }
             }
         }
@@ -347,6 +404,3 @@
         private val SEEN_TIMEOUT = 5.seconds
     }
 }
-
-private val TransitionStep.isScreenTurningOff: Boolean
-    get() = transitionState == TransitionState.STARTED && to != KeyguardState.GONE
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 4c33524..788659e 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
@@ -19,6 +19,7 @@
 import com.android.systemui.log.LogBuffer
 import com.android.systemui.log.core.LogLevel
 import com.android.systemui.log.dagger.UnseenNotificationLog
+import com.android.systemui.statusbar.notification.collection.NotificationEntry
 import javax.inject.Inject
 
 private const val TAG = "KeyguardCoordinator"
@@ -28,11 +29,14 @@
 constructor(
     @UnseenNotificationLog private val buffer: LogBuffer,
 ) {
-    fun logSeenOnLockscreen() =
+    fun logSeenOnLockscreen(entry: NotificationEntry) =
         buffer.log(
             TAG,
             LogLevel.DEBUG,
-            "Notifications on lockscreen will be marked as seen when unlocked."
+            messageInitializer = { str1 = entry.key },
+            messagePrinter = {
+                "Notification [$str1] on lockscreen will be marked as seen when unlocked."
+            },
         )
 
     fun logTrackingUnseen(trackingUnseen: Boolean) =
@@ -43,11 +47,21 @@
             messagePrinter = { "${if (bool1) "Start" else "Stop"} tracking unseen notifications." },
         )
 
-    fun logAllMarkedSeenOnUnlock() =
+    fun logAllMarkedSeenOnUnlock(
+        seenCount: Int,
+        remainingUnseenCount: Int,
+    ) =
         buffer.log(
             TAG,
             LogLevel.DEBUG,
-            "Notifications have been marked as seen now that device is unlocked."
+            messageInitializer = {
+                int1 = seenCount
+                int2 = remainingUnseenCount
+            },
+            messagePrinter = {
+                "$int1 Notifications have been marked as seen now that device is unlocked. " +
+                    "$int2 notifications remain unseen."
+            },
         )
 
     fun logShadeExpanded() =
@@ -96,4 +110,60 @@
             messageInitializer = { str1 = key },
             messagePrinter = { "Unseen notif has become heads up: $str1" },
         )
+
+    fun logTrackingLockscreenSeenDuration(unseenNotifications: Set<NotificationEntry>) {
+        buffer.log(
+            TAG,
+            LogLevel.DEBUG,
+            messageInitializer = {
+                str1 = unseenNotifications.joinToString { it.key }
+                int1 = unseenNotifications.size
+            },
+            messagePrinter = {
+                "Tracking $int1 unseen notifications for lockscreen seen duration threshold: $str1"
+            },
+        )
+    }
+
+    fun logTrackingLockscreenSeenDuration(entry: NotificationEntry) {
+        buffer.log(
+            TAG,
+            LogLevel.DEBUG,
+            messageInitializer = { str1 = entry.key },
+            messagePrinter = {
+                "Tracking new notification for lockscreen seen duration threshold: $str1"
+            },
+        )
+    }
+
+    fun logStopTrackingLockscreenSeenDuration(entry: NotificationEntry) {
+        buffer.log(
+            TAG,
+            LogLevel.DEBUG,
+            messageInitializer = { str1 = entry.key },
+            messagePrinter = {
+                "Stop tracking removed notification for lockscreen seen duration threshold: $str1"
+            },
+        )
+    }
+
+    fun logResetSeenOnLockscreen(entry: NotificationEntry) {
+        buffer.log(
+            TAG,
+            LogLevel.DEBUG,
+            messageInitializer = { str1 = entry.key },
+            messagePrinter = {
+                "Reset tracking updated notification for lockscreen seen duration threshold: $str1"
+            },
+        )
+    }
+
+    fun logRemoveSeenOnLockscreen(entry: NotificationEntry) {
+        buffer.log(
+            TAG,
+            LogLevel.DEBUG,
+            messageInitializer = { str1 = entry.key },
+            messagePrinter = { "Notification marked as seen on lockscreen removed: $str1" },
+        )
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java
index 8af488e..27510d4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java
@@ -359,9 +359,9 @@
     }
 
     @Override
-    public long performRemoveAnimation(long duration, long delay,
-            float translationDirection, boolean isHeadsUpAnimation, float endLocation,
-            Runnable onFinishedRunnable, AnimatorListenerAdapter animationListener) {
+    public long performRemoveAnimation(long duration, long delay, float translationDirection,
+            boolean isHeadsUpAnimation, Runnable onFinishedRunnable,
+            AnimatorListenerAdapter animationListener) {
         enableAppearDrawing(true);
         mIsHeadsUpAnimation = isHeadsUpAnimation;
         if (mDrawingAppearAnimation) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
index 30747db..b34c281 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
@@ -2975,7 +2975,6 @@
             long delay,
             float translationDirection,
             boolean isHeadsUpAnimation,
-            float endLocation,
             Runnable onFinishedRunnable,
             AnimatorListenerAdapter animationListener) {
         if (mMenuRow != null && mMenuRow.isMenuVisible()) {
@@ -2986,7 +2985,7 @@
                     public void onAnimationEnd(Animator animation) {
                         ExpandableNotificationRow.super.performRemoveAnimation(
                                 duration, delay, translationDirection, isHeadsUpAnimation,
-                                endLocation, onFinishedRunnable, animationListener);
+                                onFinishedRunnable, animationListener);
                     }
                 });
                 anim.start();
@@ -2994,7 +2993,7 @@
             }
         }
         return super.performRemoveAnimation(duration, delay, translationDirection,
-                isHeadsUpAnimation, endLocation, onFinishedRunnable, animationListener);
+                isHeadsUpAnimation, onFinishedRunnable, animationListener);
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java
index f0e15c2..f986244 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java
@@ -367,7 +367,6 @@
      *                             such that the  child appears to be going away to the top. 1
      *                             Should mean the opposite.
      * @param isHeadsUpAnimation Is this a headsUp animation.
-     * @param endLocation The location where the horizonal heads up disappear animation should end.
      * @param onFinishedRunnable A runnable which should be run when the animation is finished.
      * @param animationListener An animation listener to add to the animation.
      *
@@ -375,7 +374,7 @@
      * animation starts.
      */
     public abstract long performRemoveAnimation(long duration,
-            long delay, float translationDirection, boolean isHeadsUpAnimation, float endLocation,
+            long delay, float translationDirection, boolean isHeadsUpAnimation,
             Runnable onFinishedRunnable,
             AnimatorListenerAdapter animationListener);
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/StackScrollerDecorView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/StackScrollerDecorView.java
index b24cec1..0c686be 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/StackScrollerDecorView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/StackScrollerDecorView.java
@@ -235,7 +235,7 @@
 
     @Override
     public long performRemoveAnimation(long duration, long delay,
-            float translationDirection, boolean isHeadsUpAnimation, float endLocation,
+            float translationDirection, boolean isHeadsUpAnimation,
             Runnable onFinishedRunnable,
             AnimatorListenerAdapter animationListener) {
         // TODO: Use duration
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/MediaContainerView.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/MediaContainerView.kt
index b8f28b5..04308b4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/MediaContainerView.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/MediaContainerView.kt
@@ -69,11 +69,14 @@
         canvas.clipPath(clipPath)
     }
 
-
-    override fun performRemoveAnimation(duration: Long, delay: Long, translationDirection: Float,
-                                        isHeadsUpAnimation: Boolean, endLocation: Float,
-                                        onFinishedRunnable: Runnable?,
-                                        animationListener: AnimatorListenerAdapter?): Long {
+    override fun performRemoveAnimation(
+            duration: Long,
+            delay: Long,
+            translationDirection: Float,
+            isHeadsUpAnimation: Boolean,
+            onFinishedRunnable: Runnable?,
+            animationListener: AnimatorListenerAdapter?
+    ): Long {
         return 0
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
index 7c66bbe5..ef7375a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
@@ -424,7 +424,8 @@
         }
     };
 
-    private final NotificationSwipeHelper.NotificationCallback mNotificationCallback =
+    @VisibleForTesting
+    final NotificationSwipeHelper.NotificationCallback mNotificationCallback =
             new NotificationSwipeHelper.NotificationCallback() {
 
                 @Override
@@ -483,10 +484,11 @@
                  */
 
                 public void handleChildViewDismissed(View view) {
+                    // The View needs to clean up the Swipe states, e.g. roundness.
+                    mView.onSwipeEnd();
                     if (mView.getClearAllInProgress()) {
                         return;
                     }
-                    mView.onSwipeEnd();
                     if (view instanceof ExpandableNotificationRow) {
                         ExpandableNotificationRow row = (ExpandableNotificationRow) view;
                         if (row.isHeadsUp()) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateAnimator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateAnimator.java
index d73919b..2742a23 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateAnimator.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateAnimator.java
@@ -27,8 +27,6 @@
 import com.android.systemui.R;
 import com.android.systemui.shared.clocks.AnimatableClockView;
 import com.android.systemui.statusbar.NotificationShelf;
-import com.android.systemui.statusbar.StatusBarIconView;
-import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
 import com.android.systemui.statusbar.notification.row.ExpandableView;
 import com.android.systemui.statusbar.notification.row.StackScrollerDecorView;
@@ -427,7 +425,7 @@
                 }
                 changingView.performRemoveAnimation(ANIMATION_DURATION_APPEAR_DISAPPEAR,
                         0 /* delay */, translationDirection, false /* isHeadsUpAppear */,
-                        0, postAnimation, null);
+                        postAnimation, null);
             } else if (event.animationType ==
                 NotificationStackScrollLayout.AnimationEvent.ANIMATION_TYPE_REMOVE_SWIPED_OUT) {
                 if (mHostLayout.isFullySwipedOut(changingView)) {
@@ -474,28 +472,12 @@
                     mTmpState.initFrom(changingView);
                     endRunnable = changingView::removeFromTransientContainer;
                 }
-                float targetLocation = 0;
                 boolean needsAnimation = true;
                 if (changingView instanceof ExpandableNotificationRow) {
                     ExpandableNotificationRow row = (ExpandableNotificationRow) changingView;
                     if (row.isDismissed()) {
                         needsAnimation = false;
                     }
-
-                    NotificationEntry entry = row.getEntry();
-                    StatusBarIconView icon = entry.getIcons().getStatusBarIcon();
-                    final StatusBarIconView centeredIcon = entry.getIcons().getCenteredIcon();
-                    if (centeredIcon != null && centeredIcon.getParent() != null) {
-                        icon = centeredIcon;
-                    }
-                    if (icon.getParent() != null) {
-                        icon.getLocationOnScreen(mTmpLocation);
-                        float iconPosition = mTmpLocation[0] - icon.getTranslationX()
-                                + ViewState.getFinalTranslationX(icon)
-                                + icon.getWidth() * 0.25f;
-                        mHostLayout.getLocationOnScreen(mTmpLocation);
-                        targetLocation = iconPosition - mTmpLocation[0];
-                    }
                 }
 
                 if (needsAnimation) {
@@ -515,7 +497,7 @@
                     }
                     long removeAnimationDelay = changingView.performRemoveAnimation(
                             ANIMATION_DURATION_HEADS_UP_DISAPPEAR,
-                            0, 0.0f, true /* isHeadsUpAppear */, targetLocation,
+                            0, 0.0f, true /* isHeadsUpAppear */,
                             postAnimation, getGlobalAnimationFinishedListener());
                     mAnimationProperties.delay += removeAnimationDelay;
                 } else if (endRunnable != null) {
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 4ba09e1..478baf2f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java
@@ -203,8 +203,6 @@
 
     int getStatusBarHeight();
 
-    boolean isShadeDisabled();
-
     boolean isLaunchingActivityOverLockscreen();
 
     void onKeyguardViewManagerStatesUpdated();
@@ -380,14 +378,6 @@
 
     void resendMessage(Object msg);
 
-    int getDisabled1();
-
-    void setDisabled1(int disabled);
-
-    int getDisabled2();
-
-    void setDisabled2(int disabled);
-
     void setLastCameraLaunchSource(int source);
 
     void setLaunchCameraOnFinishedGoingToSleep(boolean launch);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java
index 332be2a..6431ef9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java
@@ -111,6 +111,9 @@
     private static final VibrationAttributes HARDWARE_FEEDBACK_VIBRATION_ATTRIBUTES =
             VibrationAttributes.createForUsage(VibrationAttributes.USAGE_HARDWARE_FEEDBACK);
 
+    private int mDisabled1;
+    private int mDisabled2;
+
     @Inject
     CentralSurfacesCommandQueueCallbacks(
             CentralSurfaces centralSurfaces,
@@ -256,22 +259,14 @@
             return;
         }
 
-        int state2BeforeAdjustment = state2;
-        state2 = mRemoteInputQuickSettingsDisabler.adjustDisableFlags(state2);
-        Log.d(CentralSurfaces.TAG,
-                mDisableFlagsLogger.getDisableFlagsString(
-                        /* new= */ new DisableFlagsLogger.DisableState(
-                                state1, state2BeforeAdjustment),
-                        /* newStateAfterLocalModification= */ new DisableFlagsLogger.DisableState(
-                                state1, state2)));
-
-        final int old1 = mCentralSurfaces.getDisabled1();
+        final int old1 = mDisabled1;
         final int diff1 = state1 ^ old1;
-        mCentralSurfaces.setDisabled1(state1);
+        mDisabled1 = state1;
 
-        final int old2 = mCentralSurfaces.getDisabled2();
+        state2 = mRemoteInputQuickSettingsDisabler.adjustDisableFlags(state2);
+        final int old2 = mDisabled2;
         final int diff2 = state2 ^ old2;
-        mCentralSurfaces.setDisabled2(state2);
+        mDisabled2 = state2;
 
         if ((diff1 & StatusBarManager.DISABLE_EXPAND) != 0) {
             if ((state1 & StatusBarManager.DISABLE_EXPAND) != 0) {
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 7cb0042..8902a18 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
@@ -42,6 +42,7 @@
 
 import android.annotation.Nullable;
 import android.app.ActivityManager;
+import android.app.ActivityOptions;
 import android.app.IWallpaperManager;
 import android.app.KeyguardManager;
 import android.app.Notification;
@@ -362,26 +363,6 @@
     }
 
     @Override
-    public int getDisabled1() {
-        return mDisabled1;
-    }
-
-    @Override
-    public void setDisabled1(int disabled) {
-        mDisabled1 = disabled;
-    }
-
-    @Override
-    public int getDisabled2() {
-        return mDisabled2;
-    }
-
-    @Override
-    public void setDisabled2(int disabled) {
-        mDisabled2 = disabled;
-    }
-
-    @Override
     public void setLastCameraLaunchSource(int source) {
         mLastCameraLaunchSource = source;
     }
@@ -527,11 +508,6 @@
 
     private CentralSurfacesComponent mCentralSurfacesComponent;
 
-    // Flags for disabling the status bar
-    // Two variables because the first one evidently ran out of room for new flags.
-    private int mDisabled1 = 0;
-    private int mDisabled2 = 0;
-
     /**
      * This keeps track of whether we have (or haven't) registered the predictive back callback.
      * Since we can have visible -> visible transitions, we need to avoid
@@ -1721,11 +1697,6 @@
         return mStatusBarWindowController.getStatusBarHeight();
     }
 
-    @Override
-    public boolean isShadeDisabled() {
-        return (mDisabled2 & StatusBarManager.DISABLE2_NOTIFICATION_SHADE) != 0;
-    }
-
     private void updateReportRejectedTouchVisibility() {
         if (mReportRejectedTouch == null) {
             return;
@@ -1803,7 +1774,10 @@
                     EventLog.writeEvent(EventLogTags.SYSUI_HEADS_UP_ESCALATION,
                             sbn.getKey());
                     wakeUpForFullScreenIntent();
-                    notification.fullScreenIntent.send();
+                    ActivityOptions opts = ActivityOptions.makeBasic();
+                    opts.setPendingIntentBackgroundActivityStartMode(
+                            ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED);
+                    notification.fullScreenIntent.send(opts.toBundle());
                     entry.notifyFullScreenIntentLaunched();
                 } catch (PendingIntent.CanceledException e) {
                 }
@@ -2207,10 +2181,6 @@
      */
     @Override
     public void setLockscreenUser(int newUserId) {
-        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/LockscreenWallpaper.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockscreenWallpaper.java
index c07b5e0..b2c39f7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockscreenWallpaper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockscreenWallpaper.java
@@ -40,12 +40,17 @@
 
 import com.android.internal.util.IndentingPrintWriter;
 import com.android.keyguard.KeyguardUpdateMonitor;
+import com.android.systemui.CoreStartable;
 import com.android.systemui.Dumpable;
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.settings.UserTracker;
 import com.android.systemui.statusbar.NotificationMediaManager;
+import com.android.systemui.user.data.model.SelectedUserModel;
+import com.android.systemui.user.data.model.SelectionStatus;
+import com.android.systemui.user.data.repository.UserRepository;
+import com.android.systemui.util.kotlin.JavaAdapter;
 
 import libcore.io.IoUtils;
 
@@ -59,7 +64,7 @@
  */
 @SysUISingleton
 public class LockscreenWallpaper extends IWallpaperManagerCallback.Stub implements Runnable,
-        Dumpable {
+        Dumpable, CoreStartable {
 
     private static final String TAG = "LockscreenWallpaper";
 
@@ -72,6 +77,8 @@
     private final WallpaperManager mWallpaperManager;
     private final KeyguardUpdateMonitor mUpdateMonitor;
     private final Handler mH;
+    private final JavaAdapter mJavaAdapter;
+    private final UserRepository mUserRepository;
 
     private boolean mCached;
     private Bitmap mCache;
@@ -88,6 +95,8 @@
             DumpManager dumpManager,
             NotificationMediaManager mediaManager,
             @Main Handler mainHandler,
+            JavaAdapter javaAdapter,
+            UserRepository userRepository,
             UserTracker userTracker) {
         dumpManager.registerDumpable(getClass().getSimpleName(), this);
         mWallpaperManager = wallpaperManager;
@@ -95,6 +104,8 @@
         mUpdateMonitor = keyguardUpdateMonitor;
         mMediaManager = mediaManager;
         mH = mainHandler;
+        mJavaAdapter = javaAdapter;
+        mUserRepository = userRepository;
 
         if (iWallpaperManager != null && !mWallpaperManager.isLockscreenLiveWallpaperEnabled()) {
             // Service is disabled on some devices like Automotive
@@ -106,6 +117,14 @@
         }
     }
 
+    @Override
+    public void start() {
+        if (!isLockscreenLiveWallpaperEnabled()) {
+            mJavaAdapter.alwaysCollectFlow(
+                    mUserRepository.getSelectedUser(), this::setSelectedUser);
+        }
+    }
+
     public Bitmap getBitmap() {
         assertLockscreenLiveWallpaperNotEnabled();
 
@@ -169,9 +188,15 @@
         }
     }
 
-    public void setCurrentUser(int user) {
+    private void setSelectedUser(SelectedUserModel selectedUserModel) {
         assertLockscreenLiveWallpaperNotEnabled();
 
+        if (selectedUserModel.getSelectionStatus().equals(SelectionStatus.SELECTION_IN_PROGRESS)) {
+            // Wait until the selection has finished before updating.
+            return;
+        }
+
+        int user = selectedUserModel.getUserInfo().id;
         if (user != mCurrentUserId) {
             if (mSelectedUser == null || user != mSelectedUser.getIdentifier()) {
                 mCached = false;
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/dagger/StatusBarViewModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarViewModule.java
index 77381dd..ebdde78 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
@@ -27,11 +27,9 @@
 import com.android.systemui.statusbar.OperatorNameViewController;
 import com.android.systemui.statusbar.events.SystemStatusAnimationScheduler;
 import com.android.systemui.statusbar.phone.NotificationIconAreaController;
-import com.android.systemui.statusbar.phone.StatusBarBoundsProvider;
 import com.android.systemui.statusbar.phone.StatusBarHideIconsForBouncerManager;
 import com.android.systemui.statusbar.phone.StatusBarIconController;
 import com.android.systemui.statusbar.phone.StatusBarLocationPublisher;
-import com.android.systemui.statusbar.phone.SystemBarAttributesListener;
 import com.android.systemui.statusbar.phone.fragment.CollapsedStatusBarFragment;
 import com.android.systemui.statusbar.phone.fragment.CollapsedStatusBarFragmentLogger;
 import com.android.systemui.statusbar.phone.fragment.dagger.StatusBarFragmentComponent;
@@ -43,10 +41,8 @@
 import com.android.systemui.util.CarrierConfigTracker;
 import com.android.systemui.util.settings.SecureSettings;
 
-import dagger.Binds;
 import dagger.Module;
 import dagger.Provides;
-import dagger.multibindings.IntoSet;
 
 import java.util.concurrent.Executor;
 
@@ -64,11 +60,6 @@
 
     public static final String STATUS_BAR_FRAGMENT = "status_bar_fragment";
 
-    @Binds
-    @IntoSet
-    abstract StatusBarBoundsProvider.BoundsChangeListener sysBarAttrsListenerAsBoundsListener(
-            SystemBarAttributesListener systemBarAttributesListener);
-
     /**
      * Creates a new {@link CollapsedStatusBarFragment}.
      *
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/dagger/StatusBarFragmentModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/dagger/StatusBarFragmentModule.java
index 730ecde..97a1bd1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/dagger/StatusBarFragmentModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/dagger/StatusBarFragmentModule.java
@@ -26,20 +26,23 @@
 import com.android.systemui.statusbar.phone.PhoneStatusBarView;
 import com.android.systemui.statusbar.phone.PhoneStatusBarViewController;
 import com.android.systemui.statusbar.phone.StatusBarBoundsProvider;
+import com.android.systemui.statusbar.phone.SystemBarAttributesListener;
 import com.android.systemui.statusbar.phone.fragment.CollapsedStatusBarFragment;
 import com.android.systemui.statusbar.phone.userswitcher.StatusBarUserSwitcherContainer;
 import com.android.systemui.statusbar.policy.Clock;
 import com.android.systemui.statusbar.window.StatusBarWindowController;
 
+import dagger.Binds;
+import dagger.Module;
+import dagger.Provides;
+import dagger.multibindings.IntoSet;
+import dagger.multibindings.Multibinds;
+
 import java.util.Optional;
 import java.util.Set;
 
 import javax.inject.Named;
 
-import dagger.Module;
-import dagger.Provides;
-import dagger.multibindings.Multibinds;
-
 /** Dagger module for {@link StatusBarFragmentComponent}. */
 @Module
 public interface StatusBarFragmentModule {
@@ -151,4 +154,10 @@
     /** */
     @Multibinds
     Set<StatusBarBoundsProvider.BoundsChangeListener> boundsChangeListeners();
+
+    /** */
+    @Binds
+    @IntoSet
+    StatusBarBoundsProvider.BoundsChangeListener sysBarAttrsListenerAsBoundsListener(
+            SystemBarAttributesListener systemBarAttributesListener);
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryImpl.kt
index 74352d29..54948a4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryImpl.kt
@@ -250,15 +250,8 @@
             .distinctUntilChanged()
             .onEach { logger.logDefaultMobileIconGroup(it) }
 
-    override fun getRepoForSubId(subId: Int): FullMobileConnectionRepository {
-        if (!isValidSubId(subId)) {
-            throw IllegalArgumentException(
-                "subscriptionId $subId is not in the list of valid subscriptions"
-            )
-        }
-
-        return getOrCreateRepoForSubId(subId)
-    }
+    override fun getRepoForSubId(subId: Int): FullMobileConnectionRepository =
+        getOrCreateRepoForSubId(subId)
 
     private fun getOrCreateRepoForSubId(subId: Int) =
         subIdRepositoryCache[subId]
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/demo/DemoWifiRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/demo/DemoWifiRepository.kt
index a4fbc2c..a57be66 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/demo/DemoWifiRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/demo/DemoWifiRepository.kt
@@ -96,7 +96,7 @@
             networkId = DEMO_NET_ID,
             isValidated = validated ?: true,
             level = level ?: 0,
-            ssid = ssid,
+            ssid = ssid ?: DEMO_NET_SSID,
 
             // These fields below aren't supported in demo mode, since they aren't needed to satisfy
             // the interface.
@@ -115,5 +115,6 @@
 
     companion object {
         private const val DEMO_NET_ID = 1234
+        private const val DEMO_NET_SSID = "Demo SSID"
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputViewController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputViewController.kt
index 22b4c9d..736b145 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputViewController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputViewController.kt
@@ -16,6 +16,7 @@
 
 package com.android.systemui.statusbar.policy
 
+import android.app.ActivityOptions
 import android.app.Notification
 import android.app.PendingIntent
 import android.app.RemoteInput
@@ -275,7 +276,10 @@
                 entry.sbn.instanceId)
 
         try {
-            pendingIntent.send(view.context, 0, intent)
+            val options = ActivityOptions.makeBasic()
+            options.setPendingIntentBackgroundActivityStartMode(
+                    ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED)
+            pendingIntent.send(view.context, 0, intent, null, null, null, options.toBundle())
         } catch (e: PendingIntent.CanceledException) {
             Log.i(TAG, "Unable to send remote input result", e)
             uiEventLogger.logWithInstanceId(
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 cac5e32..1776e5b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyStateInflater.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyStateInflater.kt
@@ -16,6 +16,7 @@
 
 package com.android.systemui.statusbar.policy
 
+import android.app.ActivityOptions
 import android.app.Notification
 import android.app.Notification.Action.SEMANTIC_ACTION_MARK_CONVERSATION_AS_PRIORITY
 import android.app.PendingIntent
@@ -491,7 +492,11 @@
             entry.setHasSentReply()
             try {
                 val intent = createRemoteInputIntent(smartReplies, choice)
-                smartReplies.pendingIntent.send(context, 0, intent)
+                val opts = ActivityOptions.makeBasic()
+                opts.setPendingIntentBackgroundActivityStartMode(
+                        ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED)
+                smartReplies.pendingIntent.send(context, 0, intent, /* onFinished */null,
+                        /* handler */ null, /* requiredPermission */ null, opts.toBundle())
             } catch (e: PendingIntent.CanceledException) {
                 Log.w(TAG, "Unable to send smart reply", e)
             }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ZenModeControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ZenModeControllerImpl.java
index b135d0d..1c3a8850 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ZenModeControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ZenModeControllerImpl.java
@@ -28,6 +28,7 @@
 import android.net.Uri;
 import android.os.Handler;
 import android.os.HandlerExecutor;
+import android.os.Trace;
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.provider.Settings.Global;
@@ -122,7 +123,12 @@
                 userTracker.getUserId()) {
             @Override
             protected void handleValueChanged(int value, boolean observedChange) {
-                updateZenModeConfig();
+                try {
+                    Trace.beginSection("updateZenModeConfig");
+                    updateZenModeConfig();
+                } finally {
+                    Trace.endSection();
+                }
             }
         };
         mNoMan = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/tv/notifications/TvNotificationAdapter.java b/packages/SystemUI/src/com/android/systemui/statusbar/tv/notifications/TvNotificationAdapter.java
index 3362097..fd7c30f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/tv/notifications/TvNotificationAdapter.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/tv/notifications/TvNotificationAdapter.java
@@ -103,6 +103,8 @@
                 if (mPendingIntent != null) {
                     BroadcastOptions options = BroadcastOptions.makeBasic();
                     options.setInteractive(true);
+                    options.setPendingIntentBackgroundActivityStartMode(
+                            BroadcastOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED);
                     mPendingIntent.send(options.toBundle());
                 }
             } catch (PendingIntent.CanceledException e) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowController.java b/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowController.java
index bcf3b0c..4a9921e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowController.java
@@ -240,8 +240,10 @@
                     Insets.of(0, safeTouchRegionHeight, 0, 0));
         }
         lp.providedInsets = new InsetsFrameProvider[] {
-                new InsetsFrameProvider(mInsetsSourceOwner, 0, statusBars()),
-                new InsetsFrameProvider(mInsetsSourceOwner, 0, tappableElement()),
+                new InsetsFrameProvider(mInsetsSourceOwner, 0, statusBars())
+                        .setInsetsSize(Insets.of(0, height, 0, 0)),
+                new InsetsFrameProvider(mInsetsSourceOwner, 0, tappableElement())
+                        .setInsetsSize(Insets.of(0, height, 0, 0)),
                 gestureInsetsProvider
         };
         return lp;
diff --git a/packages/SystemUI/src/com/android/systemui/user/data/model/SelectedUserModel.kt b/packages/SystemUI/src/com/android/systemui/user/data/model/SelectedUserModel.kt
new file mode 100644
index 0000000..cefd466
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/user/data/model/SelectedUserModel.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.user.data.model
+
+import android.content.pm.UserInfo
+
+/** A model for the currently selected user. */
+data class SelectedUserModel(
+    /** Information about the user. */
+    val userInfo: UserInfo,
+    /** The current status of the selection. */
+    val selectionStatus: SelectionStatus,
+)
+
+/** The current status of the selection. */
+enum class SelectionStatus {
+    /** This user has started being selected but the selection hasn't completed. */
+    SELECTION_IN_PROGRESS,
+    /** The selection of this user has completed. */
+    SELECTION_COMPLETE,
+}
diff --git a/packages/SystemUI/src/com/android/systemui/user/data/repository/UserRepository.kt b/packages/SystemUI/src/com/android/systemui/user/data/repository/UserRepository.kt
index 3de75ca..954765c 100644
--- a/packages/SystemUI/src/com/android/systemui/user/data/repository/UserRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/user/data/repository/UserRepository.kt
@@ -33,6 +33,8 @@
 import com.android.systemui.flags.FeatureFlags
 import com.android.systemui.flags.Flags.FACE_AUTH_REFACTOR
 import com.android.systemui.settings.UserTracker
+import com.android.systemui.user.data.model.SelectedUserModel
+import com.android.systemui.user.data.model.SelectionStatus
 import com.android.systemui.user.data.model.UserSwitcherSettingsModel
 import com.android.systemui.util.settings.GlobalSettings
 import com.android.systemui.util.settings.SettingsProxyExt.observerFlow
@@ -69,6 +71,9 @@
     /** List of all users on the device. */
     val userInfos: Flow<List<UserInfo>>
 
+    /** Information about the currently-selected user, including [UserInfo] and other details. */
+    val selectedUser: StateFlow<SelectedUserModel>
+
     /** [UserInfo] of the currently-selected user. */
     val selectedUserInfo: Flow<UserInfo>
 
@@ -146,9 +151,6 @@
     private val _userInfos = MutableStateFlow<List<UserInfo>?>(null)
     override val userInfos: Flow<List<UserInfo>> = _userInfos.filterNotNull()
 
-    private val _selectedUserInfo = MutableStateFlow<UserInfo?>(null)
-    override val selectedUserInfo: Flow<UserInfo> = _selectedUserInfo.filterNotNull()
-
     override var mainUserId: Int = UserHandle.USER_NULL
         private set
     override var lastSelectedNonGuestUserId: Int = UserHandle.USER_NULL
@@ -174,12 +176,57 @@
     override var isRefreshUsersPaused: Boolean = false
 
     init {
-        observeSelectedUser()
         if (featureFlags.isEnabled(FACE_AUTH_REFACTOR)) {
             observeUserSwitching()
         }
     }
 
+    override val selectedUser: StateFlow<SelectedUserModel> = run {
+        // Some callbacks don't modify the selection status, so maintain the current value.
+        var currentSelectionStatus = SelectionStatus.SELECTION_COMPLETE
+        conflatedCallbackFlow {
+                fun send(selectionStatus: SelectionStatus) {
+                    currentSelectionStatus = selectionStatus
+                    trySendWithFailureLogging(
+                        SelectedUserModel(tracker.userInfo, selectionStatus),
+                        TAG,
+                    )
+                }
+
+                val callback =
+                    object : UserTracker.Callback {
+                        override fun onUserChanging(newUser: Int, userContext: Context) {
+                            send(SelectionStatus.SELECTION_IN_PROGRESS)
+                        }
+
+                        override fun onUserChanged(newUser: Int, userContext: Context) {
+                            send(SelectionStatus.SELECTION_COMPLETE)
+                        }
+
+                        override fun onProfilesChanged(profiles: List<UserInfo>) {
+                            send(currentSelectionStatus)
+                        }
+                    }
+
+                tracker.addCallback(callback, mainDispatcher.asExecutor())
+                send(currentSelectionStatus)
+
+                awaitClose { tracker.removeCallback(callback) }
+            }
+            .onEach {
+                if (!it.userInfo.isGuest) {
+                    lastSelectedNonGuestUserId = it.userInfo.id
+                }
+            }
+            .stateIn(
+                applicationScope,
+                SharingStarted.Eagerly,
+                initialValue = SelectedUserModel(tracker.userInfo, currentSelectionStatus)
+            )
+    }
+
+    override val selectedUserInfo: Flow<UserInfo> = selectedUser.map { it.userInfo }
+
     override fun refreshUsers() {
         applicationScope.launch {
             val result = withContext(backgroundDispatcher) { manager.aliveUsers }
@@ -201,7 +248,7 @@
     }
 
     override fun getSelectedUserInfo(): UserInfo {
-        return checkNotNull(_selectedUserInfo.value)
+        return selectedUser.value.userInfo
     }
 
     override fun isSimpleUserSwitcher(): Boolean {
@@ -234,38 +281,6 @@
             .launchIn(applicationScope)
     }
 
-    private fun observeSelectedUser() {
-        conflatedCallbackFlow {
-                fun send() {
-                    trySendWithFailureLogging(tracker.userInfo, TAG)
-                }
-
-                val callback =
-                    object : UserTracker.Callback {
-                        override fun onUserChanging(newUser: Int, userContext: Context) {
-                            send()
-                        }
-
-                        override fun onProfilesChanged(profiles: List<UserInfo>) {
-                            send()
-                        }
-                    }
-
-                tracker.addCallback(callback, mainDispatcher.asExecutor())
-                send()
-
-                awaitClose { tracker.removeCallback(callback) }
-            }
-            .onEach {
-                if (!it.isGuest) {
-                    lastSelectedNonGuestUserId = it.id
-                }
-
-                _selectedUserInfo.value = it
-            }
-            .launchIn(applicationScope)
-    }
-
     private suspend fun getSettings(): UserSwitcherSettingsModel {
         return withContext(backgroundDispatcher) {
             val isSimpleUserSwitcher =
diff --git a/packages/SystemUI/src/com/android/systemui/util/kotlin/PackageManagerExt.kt b/packages/SystemUI/src/com/android/systemui/util/kotlin/PackageManagerExt.kt
index 891ee0c..e32ed5f 100644
--- a/packages/SystemUI/src/com/android/systemui/util/kotlin/PackageManagerExt.kt
+++ b/packages/SystemUI/src/com/android/systemui/util/kotlin/PackageManagerExt.kt
@@ -16,12 +16,19 @@
 
 package com.android.systemui.util.kotlin
 
+import android.annotation.UserHandleAware
 import android.annotation.WorkerThread
 import android.content.pm.ComponentInfo
 import android.content.pm.PackageManager
 import com.android.systemui.util.Assert
 
+/**
+ * Determines whether a component is actually enabled (not just its default value).
+ *
+ * @throws IllegalArgumentException if the component is not found
+ */
 @WorkerThread
+@UserHandleAware
 fun PackageManager.isComponentActuallyEnabled(componentInfo: ComponentInfo): Boolean {
     Assert.isNotMainThread()
     return when (getComponentEnabledSetting(componentInfo.componentName)) {
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
index 7456d34..9362220 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
@@ -476,7 +476,8 @@
         mWindow.setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT));
         mWindow.clearFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND
                 | WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR);
-        mWindow.addFlags(WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
+        mWindow.addFlags(WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
+                | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
                 | WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED
                 | WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH
                 | WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED);
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt
index 0dcd404..2318988 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt
@@ -21,21 +21,22 @@
 import android.widget.TextView
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
-import com.android.systemui.broadcast.BroadcastDispatcher
-import com.android.systemui.flags.FeatureFlags
 import com.android.systemui.bouncer.data.repository.FakeKeyguardBouncerRepository
+import com.android.systemui.broadcast.BroadcastDispatcher
+import com.android.systemui.common.ui.data.repository.FakeConfigurationRepository
+import com.android.systemui.flags.FeatureFlags
 import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
 import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
 import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
-import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractorFactory
+import com.android.systemui.log.LogBuffer
 import com.android.systemui.plugins.ClockAnimations
 import com.android.systemui.plugins.ClockController
 import com.android.systemui.plugins.ClockEvents
-import com.android.systemui.plugins.ClockFaceController
 import com.android.systemui.plugins.ClockFaceConfig
+import com.android.systemui.plugins.ClockFaceController
 import com.android.systemui.plugins.ClockFaceEvents
 import com.android.systemui.plugins.ClockTickRate
-import com.android.systemui.log.LogBuffer
 import com.android.systemui.statusbar.CommandQueue
 import com.android.systemui.statusbar.policy.BatteryController
 import com.android.systemui.statusbar.policy.ConfigurationController
@@ -64,7 +65,6 @@
 import org.mockito.junit.MockitoJUnit
 import java.util.TimeZone
 import java.util.concurrent.Executor
-import com.android.systemui.common.ui.data.repository.FakeConfigurationRepository
 import org.mockito.Mockito.`when` as whenever
 
 @RunWith(AndroidTestingRunner::class)
@@ -122,7 +122,9 @@
                 bouncerRepository = bouncerRepository,
                 configurationRepository = FakeConfigurationRepository(),
             ),
-            KeyguardTransitionInteractor(transitionRepository, TestScope().backgroundScope),
+            KeyguardTransitionInteractorFactory.create(
+                    scope = TestScope().backgroundScope,
+            ).keyguardTransitionInteractor,
             broadcastDispatcher,
             batteryController,
             keyguardUpdateMonitor,
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java
index b21cc6d..9e561ed 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java
@@ -408,4 +408,18 @@
                 any(ClockRegistry.ClockChangeListener.class));
         verify(mClockEventController, times).registerListeners(mView);
     }
+
+    @Test
+    public void testSplitShadeEnabledSetToSmartspaceController() {
+        mController.setSplitShadeEnabled(true);
+        verify(mSmartspaceController, times(1)).setSplitShadeEnabled(true);
+        verify(mSmartspaceController, times(0)).setSplitShadeEnabled(false);
+    }
+
+    @Test
+    public void testSplitShadeDisabledSetToSmartspaceController() {
+        mController.setSplitShadeEnabled(false);
+        verify(mSmartspaceController, times(1)).setSplitShadeEnabled(false);
+        verify(mSmartspaceController, times(0)).setSplitShadeEnabled(true);
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinViewControllerTest.kt
index 5a56baf..d3b4190 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinViewControllerTest.kt
@@ -97,7 +97,21 @@
         `when`(keyguardPinView.findViewById<NumPadButton>(R.id.delete_button))
             .thenReturn(deleteButton)
         `when`(keyguardPinView.findViewById<View>(R.id.key_enter)).thenReturn(enterButton)
-        constructViewController()
+        pinViewController =
+            KeyguardPinViewController(
+                keyguardPinView,
+                keyguardUpdateMonitor,
+                securityMode,
+                lockPatternUtils,
+                mKeyguardSecurityCallback,
+                keyguardMessageAreaControllerFactory,
+                mLatencyTracker,
+                liftToActivateListener,
+                mEmergencyButtonController,
+                falsingCollector,
+                postureController,
+                featureFlags
+            )
     }
 
     @Test
@@ -121,10 +135,8 @@
         `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)
@@ -138,10 +150,8 @@
         `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)
@@ -153,22 +163,4 @@
         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/KeyguardSecurityContainerControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java
index e561f1f..58b1edc 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java
@@ -62,12 +62,12 @@
 import com.android.systemui.SysuiTestCase;
 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.FakeFeatureFlags;
 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;
@@ -395,6 +395,7 @@
 
         // WHEN a request is made from the SimPin screens to show the next security method
         when(mKeyguardSecurityModel.getSecurityMode(TARGET_USER_ID)).thenReturn(SecurityMode.None);
+        when(mLockPatternUtils.isLockScreenDisabled(anyInt())).thenReturn(true);
         mKeyguardSecurityContainerController.showNextSecurityScreenOrFinish(
                 /* authenticated= */true,
                 TARGET_USER_ID,
@@ -423,6 +424,28 @@
     }
 
     @Test
+    public void showNextSecurityScreenOrFinish_SimPin_Swipe() {
+        // GIVEN the current security method is SimPin
+        when(mKeyguardUpdateMonitor.getUserHasTrust(anyInt())).thenReturn(false);
+        when(mKeyguardUpdateMonitor.getUserUnlockedWithBiometric(TARGET_USER_ID)).thenReturn(false);
+        mKeyguardSecurityContainerController.showSecurityScreen(SecurityMode.SimPin);
+
+        // WHEN a request is made from the SimPin screens to show the next security method
+        when(mKeyguardSecurityModel.getSecurityMode(TARGET_USER_ID)).thenReturn(SecurityMode.None);
+        // WHEN security method is SWIPE
+        when(mLockPatternUtils.isLockScreenDisabled(anyInt())).thenReturn(false);
+        mKeyguardSecurityContainerController.showNextSecurityScreenOrFinish(
+                /* authenticated= */true,
+                TARGET_USER_ID,
+                /* bypassSecondaryLockScreen= */true,
+                SecurityMode.SimPin);
+
+        // THEN the next security method of None will dismiss keyguard.
+        verify(mViewMediatorCallback, never()).keyguardDone(anyBoolean(), anyInt());
+    }
+
+
+    @Test
     public void onSwipeUp_whenFaceDetectionIsNotRunning_initiatesFaceAuth() {
         KeyguardSecurityContainer.SwipeListener registeredSwipeListener =
                 getRegisteredSwipeListener();
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerTest.java
index a2c6329..512e5dc 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerTest.java
@@ -17,6 +17,7 @@
 package com.android.keyguard;
 
 import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
@@ -155,4 +156,18 @@
         verify(mControllerMock).setProperty(AnimatableProperty.SCALE_X, 20f, true);
         verify(mControllerMock).setProperty(AnimatableProperty.SCALE_Y, 20f, true);
     }
+
+    @Test
+    public void splitShadeEnabledPassedToClockSwitchController() {
+        mController.setSplitShadeEnabled(true);
+        verify(mKeyguardClockSwitchController, times(1)).setSplitShadeEnabled(true);
+        verify(mKeyguardClockSwitchController, times(0)).setSplitShadeEnabled(false);
+    }
+
+    @Test
+    public void splitShadeDisabledPassedToClockSwitchController() {
+        mController.setSplitShadeEnabled(false);
+        verify(mKeyguardClockSwitchController, times(1)).setSplitShadeEnabled(false);
+        verify(mKeyguardClockSwitchController, times(0)).setSplitShadeEnabled(true);
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
index 4263091..5abab62 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
@@ -3002,6 +3002,16 @@
     }
 
     @Test
+    public void testOnSimStateChanged_HandleSimStateNotReady() {
+        KeyguardUpdateMonitorCallback keyguardUpdateMonitorCallback = spy(
+                KeyguardUpdateMonitorCallback.class);
+        mKeyguardUpdateMonitor.registerCallback(keyguardUpdateMonitorCallback);
+        mKeyguardUpdateMonitor.handleSimStateChange(-1, 0, TelephonyManager.SIM_STATE_NOT_READY);
+        verify(keyguardUpdateMonitorCallback).onSimStateChanged(-1, 0,
+                TelephonyManager.SIM_STATE_NOT_READY);
+    }
+
+    @Test
     public void onAuthEnrollmentChangesCallbacksAreNotified() {
         KeyguardUpdateMonitorCallback callback = mock(KeyguardUpdateMonitorCallback.class);
         ArgumentCaptor<AuthController.Callback> authCallback = ArgumentCaptor.forClass(
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/LockIconViewControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/keyguard/LockIconViewControllerBaseTest.java
index c88c4d6..75106e7 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/LockIconViewControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/LockIconViewControllerBaseTest.java
@@ -19,6 +19,7 @@
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
 import static com.android.systemui.flags.Flags.DOZING_MIGRATION_1;
 import static com.android.systemui.flags.Flags.FACE_AUTH_REFACTOR;
+import static com.android.systemui.flags.Flags.MIGRATE_LOCK_ICON;
 
 import static org.mockito.Mockito.any;
 import static org.mockito.Mockito.anyInt;
@@ -41,6 +42,7 @@
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.biometrics.AuthController;
 import com.android.systemui.biometrics.AuthRippleController;
+import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor;
 import com.android.systemui.doze.util.BurnInHelperKt;
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.flags.FakeFeatureFlags;
@@ -92,6 +94,7 @@
     protected @Mock KeyguardTransitionRepository mTransitionRepository;
     protected FakeExecutor mDelayableExecutor = new FakeExecutor(new FakeSystemClock());
     protected FakeFeatureFlags mFeatureFlags;
+    protected @Mock PrimaryBouncerInteractor mPrimaryBouncerInteractor;
 
     protected LockIconViewController mUnderTest;
 
@@ -143,6 +146,7 @@
 
         mFeatureFlags = new FakeFeatureFlags();
         mFeatureFlags.set(FACE_AUTH_REFACTOR, false);
+        mFeatureFlags.set(MIGRATE_LOCK_ICON, false);
         mUnderTest = new LockIconViewController(
                 mLockIconView,
                 mStatusBarStateController,
@@ -161,7 +165,8 @@
                 new KeyguardTransitionInteractor(mTransitionRepository,
                         TestScopeProvider.getTestScope().getBackgroundScope()),
                 KeyguardInteractorFactory.create(mFeatureFlags).getKeyguardInteractor(),
-                mFeatureFlags
+                mFeatureFlags,
+                mPrimaryBouncerInteractor
         );
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/LockIconViewControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/LockIconViewControllerTest.java
index b6287598..ed6a891 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/LockIconViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/LockIconViewControllerTest.java
@@ -33,6 +33,7 @@
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
 import android.util.Pair;
+import android.view.View;
 
 import androidx.test.filters.SmallTest;
 
@@ -267,4 +268,75 @@
         // THEN the lock icon is shown
         verify(mLockIconView).setContentDescription(LOCKED_LABEL);
     }
+
+    @Test
+    public void lockIconAccessibility_notVisibleToUser() {
+        // GIVEN lock icon controller is initialized and view is attached
+        init(/* useMigrationFlag= */false);
+        captureKeyguardStateCallback();
+        captureKeyguardUpdateMonitorCallback();
+
+        // GIVEN user has unlocked with a biometric auth (ie: face auth)
+        // and biometric running state changes
+        when(mKeyguardUpdateMonitor.getUserUnlockedWithBiometric(anyInt())).thenReturn(true);
+        mKeyguardUpdateMonitorCallback.onBiometricRunningStateChanged(false,
+                BiometricSourceType.FACE);
+        reset(mLockIconView);
+        when(mLockIconView.isVisibleToUser()).thenReturn(false);
+
+        // WHEN the unlocked state changes
+        when(mKeyguardUpdateMonitor.getUserUnlockedWithBiometric(anyInt())).thenReturn(false);
+        mKeyguardStateCallback.onUnlockedChanged();
+
+        // THEN the lock icon is shown
+        verify(mLockIconView).setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_NO);
+    }
+
+    @Test
+    public void lockIconAccessibility_bouncerAnimatingAway() {
+        // GIVEN lock icon controller is initialized and view is attached
+        init(/* useMigrationFlag= */false);
+        captureKeyguardStateCallback();
+        captureKeyguardUpdateMonitorCallback();
+
+        // GIVEN user has unlocked with a biometric auth (ie: face auth)
+        // and biometric running state changes
+        when(mKeyguardUpdateMonitor.getUserUnlockedWithBiometric(anyInt())).thenReturn(true);
+        mKeyguardUpdateMonitorCallback.onBiometricRunningStateChanged(false,
+                BiometricSourceType.FACE);
+        reset(mLockIconView);
+        when(mLockIconView.isVisibleToUser()).thenReturn(true);
+        when(mPrimaryBouncerInteractor.isAnimatingAway()).thenReturn(true);
+
+        // WHEN the unlocked state changes
+        when(mKeyguardUpdateMonitor.getUserUnlockedWithBiometric(anyInt())).thenReturn(false);
+        mKeyguardStateCallback.onUnlockedChanged();
+
+        // THEN the lock icon is shown
+        verify(mLockIconView).setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_NO);
+    }
+
+    @Test
+    public void lockIconAccessibility_bouncerNotAnimatingAway_viewVisible() {
+        // GIVEN lock icon controller is initialized and view is attached
+        init(/* useMigrationFlag= */false);
+        captureKeyguardStateCallback();
+        captureKeyguardUpdateMonitorCallback();
+
+        // GIVEN user has unlocked with a biometric auth (ie: face auth)
+        // and biometric running state changes
+        when(mKeyguardUpdateMonitor.getUserUnlockedWithBiometric(anyInt())).thenReturn(true);
+        mKeyguardUpdateMonitorCallback.onBiometricRunningStateChanged(false,
+                BiometricSourceType.FACE);
+        reset(mLockIconView);
+        when(mLockIconView.isVisibleToUser()).thenReturn(true);
+        when(mPrimaryBouncerInteractor.isAnimatingAway()).thenReturn(false);
+
+        // WHEN the unlocked state changes
+        when(mKeyguardUpdateMonitor.getUserUnlockedWithBiometric(anyInt())).thenReturn(false);
+        mKeyguardStateCallback.onUnlockedChanged();
+
+        // THEN the lock icon is shown
+        verify(mLockIconView).setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_YES);
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java b/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java
index 79c87cf..796e665 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java
@@ -96,6 +96,7 @@
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.settings.FakeDisplayTracker;
 import com.android.systemui.settings.UserTracker;
+import com.android.systemui.statusbar.commandline.CommandRegistry;
 import com.android.systemui.statusbar.events.PrivacyDotViewController;
 import com.android.systemui.util.concurrency.FakeExecutor;
 import com.android.systemui.util.concurrency.FakeThreadFactory;
@@ -139,6 +140,8 @@
     @Mock
     private Display mDisplay;
     @Mock
+    private CommandRegistry mCommandRegistry;
+    @Mock
     private UserTracker mUserTracker;
     @Mock
     private PrivacyDotViewController mDotViewController;
@@ -231,8 +234,9 @@
                 mExecutor,
                 new ScreenDecorationsLogger(logcatLogBuffer("TestLogBuffer"))));
 
-        mScreenDecorations = spy(new ScreenDecorations(mContext, mExecutor, mSecureSettings,
-                mUserTracker, mDisplayTracker, mDotViewController, mThreadFactory,
+        mScreenDecorations = spy(new ScreenDecorations(mContext, mSecureSettings,
+                mCommandRegistry, mUserTracker, mDisplayTracker, mDotViewController,
+                mThreadFactory,
                 mPrivacyDotDecorProviderFactory, mFaceScanningProviderFactory,
                 new ScreenDecorationsLogger(logcatLogBuffer("TestLogBuffer")),
                 mAuthController) {
@@ -1226,8 +1230,9 @@
         mFaceScanningProviders.add(mFaceScanningDecorProvider);
         when(mFaceScanningProviderFactory.getProviders()).thenReturn(mFaceScanningProviders);
         when(mFaceScanningProviderFactory.getHasProviders()).thenReturn(true);
-        ScreenDecorations screenDecorations = new ScreenDecorations(mContext, mExecutor,
-                mSecureSettings, mUserTracker, mDisplayTracker, mDotViewController,
+        ScreenDecorations screenDecorations = new ScreenDecorations(mContext,
+                mSecureSettings, mCommandRegistry, mUserTracker, mDisplayTracker,
+                mDotViewController,
                 mThreadFactory, mPrivacyDotDecorProviderFactory, mFaceScanningProviderFactory,
                 new ScreenDecorationsLogger(logcatLogBuffer("TestLogBuffer")), mAuthController);
         screenDecorations.start();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/IWindowMagnificationConnectionTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/IWindowMagnificationConnectionTest.java
index f64db78f..caf230d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/IWindowMagnificationConnectionTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/IWindowMagnificationConnectionTest.java
@@ -16,8 +16,10 @@
 
 package com.android.systemui.accessibility;
 
+import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.doAnswer;
@@ -185,6 +187,19 @@
         verify(mMagnificationSettingsController).closeMagnificationSettings();
     }
 
+    @Test
+    public void onUserMagnificationScaleChanged() throws RemoteException {
+        final int testUserId = 1;
+        final float testScale = 3.0f;
+        mIWindowMagnificationConnection.onUserMagnificationScaleChanged(
+                testUserId, TEST_DISPLAY, testScale);
+        waitForIdleSync();
+
+        assertTrue(mWindowMagnification.mUsersScales.contains(testUserId));
+        assertEquals(mWindowMagnification.mUsersScales.get(testUserId).get(TEST_DISPLAY),
+                (Float) testScale);
+    }
+
     private class FakeControllerSupplier extends
             DisplayIdIndexSupplier<WindowMagnificationController> {
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/fontscaling/FontScalingDialogTest.kt b/packages/SystemUI/tests/src/com/android/systemui/accessibility/fontscaling/FontScalingDialogTest.kt
index a10f5dd..d5e6881 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/fontscaling/FontScalingDialogTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/fontscaling/FontScalingDialogTest.kt
@@ -25,6 +25,7 @@
 import com.android.systemui.R
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.common.ui.view.SeekBarWithIconButtonsView
+import com.android.systemui.common.ui.view.SeekBarWithIconButtonsView.OnSeekBarWithIconButtonsChangeListener
 import com.android.systemui.settings.UserTracker
 import com.android.systemui.util.concurrency.FakeExecutor
 import com.android.systemui.util.mockito.capture
@@ -67,7 +68,7 @@
 
     @Mock private lateinit var userTracker: UserTracker
     @Captor
-    private lateinit var seekBarChangeCaptor: ArgumentCaptor<SeekBar.OnSeekBarChangeListener>
+    private lateinit var seekBarChangeCaptor: ArgumentCaptor<OnSeekBarWithIconButtonsChangeListener>
 
     @Before
     fun setUp() {
@@ -176,16 +177,17 @@
     fun progressChanged_keyWasNotSetBefore_fontScalingHasBeenChangedIsOn() {
         fontScalingDialog.show()
 
-        val seekBarWithIconButtonsView: SeekBarWithIconButtonsView =
-            fontScalingDialog.findViewById(R.id.font_scaling_slider)!!
+        val iconStartFrame: ViewGroup = fontScalingDialog.findViewById(R.id.icon_start_frame)!!
         secureSettings.putIntForUser(
             Settings.Secure.ACCESSIBILITY_FONT_SCALING_HAS_BEEN_CHANGED,
             OFF,
             userTracker.userId
         )
 
-        // Default seekbar progress for font size is 1, set it to another progress 0
-        seekBarWithIconButtonsView.setProgress(0)
+        // Default seekbar progress for font size is 1, click start icon to decrease the progress
+        iconStartFrame.performClick()
+        backgroundDelayableExecutor.runAllReady()
+        backgroundDelayableExecutor.advanceClockToNext()
         backgroundDelayableExecutor.runAllReady()
 
         val currentSettings =
@@ -208,7 +210,7 @@
             )
             .thenReturn(slider)
         fontScalingDialog.show()
-        verify(slider).setOnSeekBarChangeListener(capture(seekBarChangeCaptor))
+        verify(slider).setOnSeekBarWithIconButtonsChangeListener(capture(seekBarChangeCaptor))
         val seekBar: SeekBar = slider.findViewById(R.id.seekbar)!!
 
         // Default seekbar progress for font size is 1, simulate dragging to 0 without
@@ -237,6 +239,15 @@
         backgroundDelayableExecutor.advanceClockToNext()
         backgroundDelayableExecutor.runAllReady()
 
+        // SeekBar interaction is finalized.
+        seekBarChangeCaptor.value.onUserInteractionFinalized(
+            seekBar,
+            OnSeekBarWithIconButtonsChangeListener.ControlUnitType.SLIDER
+        )
+        backgroundDelayableExecutor.runAllReady()
+        backgroundDelayableExecutor.advanceClockToNext()
+        backgroundDelayableExecutor.runAllReady()
+
         // Verify that the scale of font size has been updated.
         systemScale =
             systemSettings.getFloatForUser(
@@ -258,7 +269,7 @@
             )
             .thenReturn(slider)
         fontScalingDialog.show()
-        verify(slider).setOnSeekBarChangeListener(capture(seekBarChangeCaptor))
+        verify(slider).setOnSeekBarWithIconButtonsChangeListener(capture(seekBarChangeCaptor))
         val seekBar: SeekBar = slider.findViewById(R.id.seekbar)!!
 
         // Default seekbar progress for font size is 1, simulate dragging to 0 without
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsBpViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsBpViewControllerTest.kt
new file mode 100644
index 0000000..7de78a6
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsBpViewControllerTest.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.biometrics
+
+import android.testing.TestableLooper
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.RoboPilotTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.dump.DumpManager
+import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.shade.ShadeExpansionStateManager
+import com.android.systemui.statusbar.phone.SystemUIDialogManager
+import org.junit.Assert.assertFalse
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.junit.MockitoJUnit
+
+@SmallTest
+@RoboPilotTest
+@RunWith(AndroidJUnit4::class)
+@TestableLooper.RunWithLooper
+class UdfpsBpViewControllerTest : SysuiTestCase() {
+
+    @JvmField @Rule var rule = MockitoJUnit.rule()
+
+    @Mock lateinit var udfpsBpView: UdfpsBpView
+    @Mock lateinit var statusBarStateController: StatusBarStateController
+    @Mock lateinit var shadeExpansionStateManager: ShadeExpansionStateManager
+    @Mock lateinit var systemUIDialogManager: SystemUIDialogManager
+    @Mock lateinit var dumpManager: DumpManager
+
+    private lateinit var udfpsBpViewController: UdfpsBpViewController
+
+    @Before
+    fun setup() {
+        udfpsBpViewController =
+            UdfpsBpViewController(
+                udfpsBpView,
+                statusBarStateController,
+                shadeExpansionStateManager,
+                systemUIDialogManager,
+                dumpManager
+            )
+    }
+
+    @Test
+    fun testShouldNeverPauseAuth() {
+        assertFalse(udfpsBpViewController.shouldPauseAuth())
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt
index 2248755..0e0d0e3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt
@@ -43,11 +43,12 @@
 import com.android.systemui.RoboPilotTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.animation.ActivityLaunchAnimator
+import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor
+import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor
 import com.android.systemui.dump.DumpManager
 import com.android.systemui.flags.FeatureFlags
 import com.android.systemui.flags.Flags
-import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor
-import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor
+import com.android.systemui.keyguard.ui.viewmodel.UdfpsKeyguardViewModels
 import com.android.systemui.plugins.statusbar.StatusBarStateController
 import com.android.systemui.shade.ShadeExpansionStateManager
 import com.android.systemui.statusbar.LockscreenShadeTransitionController
@@ -58,6 +59,7 @@
 import com.android.systemui.statusbar.policy.KeyguardStateController
 import com.android.systemui.util.settings.SecureSettings
 import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
 import org.junit.Before
 import org.junit.Rule
 import org.junit.Test
@@ -70,6 +72,7 @@
 import org.mockito.Mockito.mock
 import org.mockito.Mockito.verify
 import org.mockito.junit.MockitoJUnit
+import javax.inject.Provider
 import org.mockito.Mockito.`when` as whenever
 
 private const val REQUEST_ID = 2L
@@ -80,6 +83,7 @@
 private const val SENSOR_WIDTH = 30
 private const val SENSOR_HEIGHT = 60
 
+@ExperimentalCoroutinesApi
 @SmallTest
 @RoboPilotTest
 @RunWith(AndroidJUnit4::class)
@@ -116,6 +120,7 @@
     @Mock private lateinit var udfpsUtils: UdfpsUtils
     @Mock private lateinit var udfpsKeyguardAccessibilityDelegate:
             UdfpsKeyguardAccessibilityDelegate
+    @Mock private lateinit var udfpsKeyguardViewModels: Provider<UdfpsKeyguardViewModels>
     @Captor private lateinit var layoutParamsCaptor: ArgumentCaptor<WindowManager.LayoutParams>
 
     private val onTouch = { _: View, _: MotionEvent, _: Boolean -> true }
@@ -148,6 +153,7 @@
             controllerCallback, onTouch, activityLaunchAnimator, featureFlags,
             primaryBouncerInteractor, alternateBouncerInteractor, isDebuggable, udfpsUtils,
             udfpsKeyguardAccessibilityDelegate,
+            udfpsKeyguardViewModels,
         )
         block()
     }
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 30e5447..58982d1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java
@@ -22,6 +22,7 @@
 import static android.view.MotionEvent.ACTION_UP;
 
 import static com.android.internal.util.FunctionalUtils.ThrowingConsumer;
+import static com.android.systemui.classifier.Classifier.UDFPS_AUTHENTICATION;
 
 import static junit.framework.Assert.assertEquals;
 import static junit.framework.Assert.assertFalse;
@@ -59,6 +60,7 @@
 import android.os.RemoteException;
 import android.os.VibrationAttributes;
 import android.testing.TestableLooper.RunWithLooper;
+import android.util.Pair;
 import android.view.LayoutInflater;
 import android.view.MotionEvent;
 import android.view.Surface;
@@ -83,13 +85,14 @@
 import com.android.systemui.biometrics.udfps.NormalizedTouchData;
 import com.android.systemui.biometrics.udfps.SinglePointerTouchProcessor;
 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.dump.DumpManager;
 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.keyguard.ui.viewmodel.UdfpsKeyguardViewModels;
 import com.android.systemui.log.SessionTracker;
 import com.android.systemui.plugins.FalsingManager;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
@@ -219,6 +222,8 @@
     private SecureSettings mSecureSettings;
     @Mock
     private UdfpsKeyguardAccessibilityDelegate mUdfpsKeyguardAccessibilityDelegate;
+    @Mock
+    private Provider<UdfpsKeyguardViewModels> mUdfpsKeyguardViewModels;
 
     // Capture listeners so that they can be used to send events
     @Captor
@@ -318,7 +323,7 @@
                 mPrimaryBouncerInteractor, mSinglePointerTouchProcessor, mSessionTracker,
                 mAlternateBouncerInteractor, mSecureSettings, mInputManager, mUdfpsUtils,
                 mock(KeyguardFaceAuthInteractor.class),
-                mUdfpsKeyguardAccessibilityDelegate);
+                mUdfpsKeyguardAccessibilityDelegate, mUdfpsKeyguardViewModels);
         verify(mFingerprintManager).setUdfpsOverlayController(mOverlayCaptor.capture());
         mOverlayController = mOverlayCaptor.getValue();
         verify(mScreenLifecycle).addObserver(mScreenObserverCaptor.capture());
@@ -1200,8 +1205,53 @@
     }
 
     @Test
+    public void fingerDown_falsingManagerInformed() throws RemoteException {
+        final Pair<TouchProcessorResult, TouchProcessorResult> touchProcessorResult =
+                givenAcceptFingerDownEvent();
+
+        // WHEN ACTION_DOWN is received
+        when(mSinglePointerTouchProcessor.processTouch(any(), anyInt(), any())).thenReturn(
+                touchProcessorResult.first);
+        MotionEvent downEvent = MotionEvent.obtain(0, 0, ACTION_DOWN, 0, 0, 0);
+        mTouchListenerCaptor.getValue().onTouch(mUdfpsView, downEvent);
+        mBiometricExecutor.runAllReady();
+        downEvent.recycle();
+
+        // THEN falsing manager is informed of the touch
+        verify(mFalsingManager).isFalseTouch(UDFPS_AUTHENTICATION);
+    }
+
+    @Test
     public void onTouch_withNewTouchDetection_shouldCallNewFingerprintManagerPath()
             throws RemoteException {
+        final Pair<TouchProcessorResult, TouchProcessorResult> processorResultDownAndUp =
+                givenAcceptFingerDownEvent();
+
+        // WHEN ACTION_DOWN is received
+        when(mSinglePointerTouchProcessor.processTouch(any(), anyInt(), any())).thenReturn(
+                processorResultDownAndUp.first);
+        MotionEvent downEvent = MotionEvent.obtain(0, 0, ACTION_DOWN, 0, 0, 0);
+        mTouchListenerCaptor.getValue().onTouch(mUdfpsView, downEvent);
+        mBiometricExecutor.runAllReady();
+        downEvent.recycle();
+
+        // AND ACTION_UP is received
+        when(mSinglePointerTouchProcessor.processTouch(any(), anyInt(), any())).thenReturn(
+                processorResultDownAndUp.second);
+        MotionEvent upEvent = MotionEvent.obtain(0, 0, MotionEvent.ACTION_UP, 0, 0, 0);
+        mTouchListenerCaptor.getValue().onTouch(mUdfpsView, upEvent);
+        mBiometricExecutor.runAllReady();
+        upEvent.recycle();
+
+        // THEN the new FingerprintManager path is invoked.
+        verify(mFingerprintManager).onPointerDown(anyLong(), anyInt(), anyInt(), anyFloat(),
+                anyFloat(), anyFloat(), anyFloat(), anyFloat(), anyLong(), anyLong(), anyBoolean());
+        verify(mFingerprintManager).onPointerUp(anyLong(), anyInt(), anyInt(), anyFloat(),
+                anyFloat(), anyFloat(), anyFloat(), anyFloat(), anyLong(), anyLong(), anyBoolean());
+    }
+
+    private Pair<TouchProcessorResult, TouchProcessorResult> givenAcceptFingerDownEvent()
+            throws RemoteException {
         final NormalizedTouchData touchData = new NormalizedTouchData(0, 0f, 0f, 0f, 0f, 0f, 0L,
                 0L);
         final TouchProcessorResult processorResultDown = new TouchProcessorResult.ProcessedTouch(
@@ -1227,27 +1277,7 @@
 
         verify(mUdfpsView).setOnTouchListener(mTouchListenerCaptor.capture());
 
-        // 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);
-        mBiometricExecutor.runAllReady();
-        downEvent.recycle();
-
-        // AND ACTION_UP is received
-        when(mSinglePointerTouchProcessor.processTouch(any(), anyInt(), any())).thenReturn(
-                processorResultUp);
-        MotionEvent upEvent = MotionEvent.obtain(0, 0, MotionEvent.ACTION_UP, 0, 0, 0);
-        mTouchListenerCaptor.getValue().onTouch(mUdfpsView, upEvent);
-        mBiometricExecutor.runAllReady();
-        upEvent.recycle();
-
-        // THEN the new FingerprintManager path is invoked.
-        verify(mFingerprintManager).onPointerDown(anyLong(), anyInt(), anyInt(), anyFloat(),
-                anyFloat(), anyFloat(), anyFloat(), anyFloat(), anyLong(), anyLong(), anyBoolean());
-        verify(mFingerprintManager).onPointerUp(anyLong(), anyInt(), anyInt(), anyFloat(),
-                anyFloat(), anyFloat(), anyFloat(), anyFloat(), anyLong(), anyLong(), anyBoolean());
+        return new Pair<>(processorResultDown, processorResultUp);
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/LogContextInteractorImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/LogContextInteractorImplTest.kt
index 263ce1a..8f8004f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/LogContextInteractorImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/LogContextInteractorImplTest.kt
@@ -7,7 +7,7 @@
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
-import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractorFactory
 import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.keyguard.shared.model.TransitionState
 import com.android.systemui.keyguard.shared.model.TransitionStep
@@ -54,10 +54,11 @@
             LogContextInteractorImpl(
                 testScope.backgroundScope,
                 foldProvider,
-                KeyguardTransitionInteractor(
-                    keyguardTransitionRepository,
-                    testScope.backgroundScope
-                ),
+                KeyguardTransitionInteractorFactory.create(
+                        repository = keyguardTransitionRepository,
+                        scope = testScope.backgroundScope,
+                    )
+                    .keyguardTransitionInteractor,
             )
     }
 
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 22ac1b6..f811ce0 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
@@ -42,6 +42,7 @@
         )
     private val underTest =
         PinBouncerViewModel(
+            applicationContext = context,
             applicationScope = testScope.backgroundScope,
             interactor =
                 utils.bouncerInteractor(
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 61432e2..608a187 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
@@ -67,6 +67,7 @@
         )
     private val underTest =
         PinBouncerViewModel(
+            applicationContext = context,
             applicationScope = testScope.backgroundScope,
             interactor = bouncerInteractor,
             isInputEnabled = MutableStateFlow(true).asStateFlow(),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/common/ui/view/SeekBarWithIconButtonsViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/common/ui/view/SeekBarWithIconButtonsViewTest.java
index afd9be5..f0006e5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/common/ui/view/SeekBarWithIconButtonsViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/common/ui/view/SeekBarWithIconButtonsViewTest.java
@@ -18,6 +18,13 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.verify;
+
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
 import android.view.ViewGroup;
@@ -28,10 +35,15 @@
 
 import com.android.systemui.R;
 import com.android.systemui.SysuiTestCase;
+import com.android.systemui.common.ui.view.SeekBarWithIconButtonsView.OnSeekBarWithIconButtonsChangeListener;
 
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.mockito.InOrder;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.MockitoAnnotations;
 
 /**
  * Tests for {@link SeekBarWithIconButtonsView}
@@ -48,14 +60,22 @@
     private SeekBar mSeekbar;
     private SeekBarWithIconButtonsView mIconDiscreteSliderLinearLayout;
 
+    @Mock
+    private OnSeekBarWithIconButtonsChangeListener mOnSeekBarChangeListener;
+
     @Before
     public void setUp() {
+        MockitoAnnotations.initMocks(this);
+
         mIconDiscreteSliderLinearLayout = new SeekBarWithIconButtonsView(mContext);
         mIconStart = mIconDiscreteSliderLinearLayout.findViewById(R.id.icon_start);
         mIconEnd = mIconDiscreteSliderLinearLayout.findViewById(R.id.icon_end);
         mIconStartFrame = mIconDiscreteSliderLinearLayout.findViewById(R.id.icon_start_frame);
         mIconEndFrame = mIconDiscreteSliderLinearLayout.findViewById(R.id.icon_end_frame);
         mSeekbar = mIconDiscreteSliderLinearLayout.findViewById(R.id.seekbar);
+
+        mIconDiscreteSliderLinearLayout.setOnSeekBarWithIconButtonsChangeListener(
+                mOnSeekBarChangeListener);
     }
 
     @Test
@@ -109,6 +129,49 @@
     }
 
     @Test
+    public void setProgress_onlyOnProgressChangedTriggeredWithFromUserFalse() {
+        reset(mOnSeekBarChangeListener);
+        mIconDiscreteSliderLinearLayout.setProgress(1);
+
+        verify(mOnSeekBarChangeListener).onProgressChanged(
+                eq(mSeekbar), /* progress= */ eq(1), /* fromUser= */ eq(false));
+        verify(mOnSeekBarChangeListener, never()).onStartTrackingTouch(/* seekBar= */ any());
+        verify(mOnSeekBarChangeListener, never()).onStopTrackingTouch(/* seekBar= */ any());
+        verify(mOnSeekBarChangeListener, never()).onUserInteractionFinalized(
+                /* seekBar= */any(), /* control= */ anyInt());
+    }
+
+    @Test
+    public void clickIconEnd_triggerCallbacksInSequence() {
+        final int magnitude = mIconDiscreteSliderLinearLayout.getChangeMagnitude();
+        mIconDiscreteSliderLinearLayout.setProgress(0);
+        reset(mOnSeekBarChangeListener);
+
+        mIconEndFrame.performClick();
+
+        InOrder inOrder = Mockito.inOrder(mOnSeekBarChangeListener);
+        inOrder.verify(mOnSeekBarChangeListener).onProgressChanged(
+                eq(mSeekbar), /* progress= */ eq(magnitude), /* fromUser= */ eq(true));
+        inOrder.verify(mOnSeekBarChangeListener).onUserInteractionFinalized(
+                eq(mSeekbar), eq(OnSeekBarWithIconButtonsChangeListener.ControlUnitType.BUTTON));
+    }
+
+    @Test
+    public void clickIconStart_triggerCallbacksInSequence() {
+        final int magnitude = mIconDiscreteSliderLinearLayout.getChangeMagnitude();
+        mIconDiscreteSliderLinearLayout.setProgress(magnitude);
+        reset(mOnSeekBarChangeListener);
+
+        mIconStartFrame.performClick();
+
+        InOrder inOrder = Mockito.inOrder(mOnSeekBarChangeListener);
+        inOrder.verify(mOnSeekBarChangeListener).onProgressChanged(
+                eq(mSeekbar), /* progress= */ eq(0), /* fromUser= */ eq(true));
+        inOrder.verify(mOnSeekBarChangeListener).onUserInteractionFinalized(
+                eq(mSeekbar), eq(OnSeekBarWithIconButtonsChangeListener.ControlUnitType.BUTTON));
+    }
+
+    @Test
     public void setProgressStateLabels_getExpectedStateDescriptionOnInitialization() {
         String[] stateLabels = new String[]{"1", "2", "3", "4", "5"};
         mIconDiscreteSliderLinearLayout.setMax(stateLabels.length);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsEditingActivityTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsEditingActivityTest.kt
index 4210675..71d2ec1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsEditingActivityTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsEditingActivityTest.kt
@@ -11,9 +11,9 @@
 import android.window.OnBackInvokedDispatcher
 import androidx.test.filters.SmallTest
 import androidx.test.rule.ActivityTestRule
-import androidx.test.runner.intercepting.SingleActivityFactory
 import com.android.systemui.R
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.activity.SingleActivityFactory
 import com.android.systemui.controls.CustomIconCache
 import com.android.systemui.controls.controller.ControlsControllerImpl
 import com.android.systemui.flags.FakeFeatureFlags
@@ -63,24 +63,19 @@
     @JvmField
     var activityRule =
         ActivityTestRule(
-            object :
-                SingleActivityFactory<TestableControlsEditingActivity>(
-                    TestableControlsEditingActivity::class.java
-                ) {
-                override fun create(intent: Intent?): TestableControlsEditingActivity {
-                    return TestableControlsEditingActivity(
-                        featureFlags,
-                        uiExecutor,
-                        controller,
-                        userTracker,
-                        customIconCache,
-                        mockDispatcher,
-                        latch
-                    )
-                }
+            /* activityFactory= */ SingleActivityFactory {
+                TestableControlsEditingActivity(
+                    featureFlags,
+                    uiExecutor,
+                    controller,
+                    userTracker,
+                    customIconCache,
+                    mockDispatcher,
+                    latch
+                )
             },
-            false,
-            false
+            /* initialTouchMode= */ false,
+            /* launchActivity= */ false,
         )
 
     @Before
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsFavoritingActivityTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsFavoritingActivityTest.kt
index f4cc8bc..f11c296 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsFavoritingActivityTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsFavoritingActivityTest.kt
@@ -13,9 +13,9 @@
 import androidx.test.filters.FlakyTest
 import androidx.test.filters.SmallTest
 import androidx.test.rule.ActivityTestRule
-import androidx.test.runner.intercepting.SingleActivityFactory
 import com.android.systemui.R
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.activity.SingleActivityFactory
 import com.android.systemui.controls.ControlStatus
 import com.android.systemui.controls.ControlsServiceInfo
 import com.android.systemui.controls.controller.ControlsController
@@ -91,24 +91,19 @@
     @JvmField
     var activityRule =
         ActivityTestRule(
-            object :
-                SingleActivityFactory<TestableControlsFavoritingActivity>(
-                    TestableControlsFavoritingActivity::class.java
-                ) {
-                override fun create(intent: Intent?): TestableControlsFavoritingActivity {
-                    return TestableControlsFavoritingActivity(
-                        featureFlags,
-                        executor,
-                        controller,
-                        listingController,
-                        userTracker,
-                        mockDispatcher,
-                        latch
-                    )
-                }
+            /* activityFactory= */ SingleActivityFactory {
+                TestableControlsFavoritingActivity(
+                    featureFlags,
+                    executor,
+                    controller,
+                    listingController,
+                    userTracker,
+                    mockDispatcher,
+                    latch
+                )
             },
-            false,
-            false
+            /* initialTouchMode= */ false,
+            /* launchActivity= */ false,
         )
 
     @Before
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsProviderSelectorActivityTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsProviderSelectorActivityTest.kt
index 4ba6718..d17495f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsProviderSelectorActivityTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsProviderSelectorActivityTest.kt
@@ -29,8 +29,8 @@
 import android.window.OnBackInvokedDispatcher
 import androidx.test.filters.SmallTest
 import androidx.test.rule.ActivityTestRule
-import androidx.test.runner.intercepting.SingleActivityFactory
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.activity.SingleActivityFactory
 import com.android.systemui.controls.ControlsServiceInfo
 import com.android.systemui.controls.controller.ControlsController
 import com.android.systemui.controls.panels.AuthorizedPanelsRepository
@@ -91,26 +91,21 @@
     @JvmField
     var activityRule =
         ActivityTestRule(
-            object :
-                SingleActivityFactory<TestableControlsProviderSelectorActivity>(
-                    TestableControlsProviderSelectorActivity::class.java
-                ) {
-                override fun create(intent: Intent?): TestableControlsProviderSelectorActivity {
-                    return TestableControlsProviderSelectorActivity(
-                        executor,
-                        backExecutor,
-                        listingController,
-                        controlsController,
-                        userTracker,
-                        authorizedPanelsRepository,
-                        dialogFactory,
-                        mockDispatcher,
-                        latch
-                    )
-                }
+            /* activityFactory= */ SingleActivityFactory {
+                TestableControlsProviderSelectorActivity(
+                    executor,
+                    backExecutor,
+                    listingController,
+                    controlsController,
+                    userTracker,
+                    authorizedPanelsRepository,
+                    dialogFactory,
+                    mockDispatcher,
+                    latch
+                )
             },
-            false,
-            false
+            /* initialTouchMode= */ false,
+            /* launchActivity= */ false,
         )
 
     @Before
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsRequestDialogTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsRequestDialogTest.kt
index 314b176..ca970bb 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsRequestDialogTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsRequestDialogTest.kt
@@ -30,8 +30,8 @@
 import androidx.lifecycle.Lifecycle
 import androidx.test.filters.MediumTest
 import androidx.test.rule.ActivityTestRule
-import androidx.test.runner.intercepting.SingleActivityFactory
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.activity.SingleActivityFactory
 import com.android.systemui.controls.controller.ControlInfo
 import com.android.systemui.controls.controller.ControlsController
 import com.android.systemui.settings.UserTracker
@@ -81,19 +81,18 @@
 
     @Rule
     @JvmField
-    var activityRule = ActivityTestRule<TestControlsRequestDialog>(
-            object : SingleActivityFactory<TestControlsRequestDialog>(
-                    TestControlsRequestDialog::class.java
-            ) {
-                    override fun create(intent: Intent?): TestControlsRequestDialog {
-                        return TestControlsRequestDialog(
-                                mainExecutor,
-                                controller,
-                                userTracker,
-                                listingController
-                        )
-                    }
-            }, false, false)
+    var activityRule = ActivityTestRule(
+        /* activityFactory= */ SingleActivityFactory {
+            TestControlsRequestDialog(
+                    mainExecutor,
+                    controller,
+                    userTracker,
+                    listingController
+            )
+        },
+        /* initialTouchMode= */ false,
+        /* launchActivity= */ false,
+    )
 
     private lateinit var control: Control
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/ui/ControlsActivityTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/ui/ControlsActivityTest.kt
index 2d3e10e..e279d28 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/controls/ui/ControlsActivityTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/ui/ControlsActivityTest.kt
@@ -23,8 +23,8 @@
 import android.testing.TestableLooper
 import androidx.test.filters.SmallTest
 import androidx.test.rule.ActivityTestRule
-import androidx.test.runner.intercepting.SingleActivityFactory
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.activity.SingleActivityFactory
 import com.android.systemui.broadcast.BroadcastDispatcher
 import com.android.systemui.controls.settings.ControlsSettingsDialogManager
 import com.android.systemui.flags.FeatureFlags
@@ -53,23 +53,18 @@
     @JvmField
     var activityRule =
         ActivityTestRule(
-            object :
-                SingleActivityFactory<TestableControlsActivity>(
-                    TestableControlsActivity::class.java
-                ) {
-                override fun create(intent: Intent?): TestableControlsActivity {
-                    return TestableControlsActivity(
-                        uiController,
-                        broadcastDispatcher,
-                        dreamManager,
-                        featureFlags,
-                        controlsSettingsDialogManager,
-                        keyguardStateController,
-                    )
-                }
+            /* activityFactory= */ SingleActivityFactory {
+                TestableControlsActivity(
+                    uiController,
+                    broadcastDispatcher,
+                    dreamManager,
+                    featureFlags,
+                    controlsSettingsDialogManager,
+                    keyguardStateController,
+                )
             },
-            false,
-            false
+            /* initialTouchMode= */ false,
+            /* launchActivity= */ false,
         )
 
     @Before
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardSliceProviderTest.java b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardSliceProviderTest.java
index 729a1cc..ce8028c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardSliceProviderTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardSliceProviderTest.java
@@ -19,6 +19,7 @@
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyString;
 import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.clearInvocations;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.reset;
@@ -31,6 +32,7 @@
 import android.media.MediaMetadata;
 import android.media.session.PlaybackState;
 import android.net.Uri;
+import android.os.Handler;
 import android.provider.Settings;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
@@ -166,6 +168,7 @@
 
     @Test
     public void updatesClock() {
+        clearInvocations(mContentResolver);
         mProvider.mKeyguardUpdateMonitorCallback.onTimeChanged();
         TestableLooper.get(this).processAllMessages();
         verify(mContentResolver).notifyChange(eq(mProvider.getUri()), eq(null));
@@ -217,11 +220,13 @@
         reset(mContentResolver);
         mProvider.onPrimaryMetadataOrStateChanged(mock(MediaMetadata.class),
                 PlaybackState.STATE_PLAYING);
+        TestableLooper.get(this).processAllMessages();
         verify(mContentResolver).notifyChange(eq(mProvider.getUri()), eq(null));
 
         // Hides after waking up
         reset(mContentResolver);
         mProvider.onDozingChanged(false);
+        TestableLooper.get(this).processAllMessages();
         verify(mContentResolver).notifyChange(eq(mProvider.getUri()), eq(null));
     }
 
@@ -231,6 +236,7 @@
         mProvider.onPrimaryMetadataOrStateChanged(mock(MediaMetadata.class),
                 PlaybackState.STATE_PLAYING);
         reset(mContentResolver);
+        TestableLooper.get(this).processAllMessages();
         // Show media when dozing
         mProvider.onDozingChanged(true);
         verify(mContentResolver).notifyChange(eq(mProvider.getUri()), eq(null));
@@ -272,6 +278,8 @@
             mMediaManager = KeyguardSliceProviderTest.this.mNotificationMediaManager;
             mKeyguardUpdateMonitor = KeyguardSliceProviderTest.this.mKeyguardUpdateMonitor;
             mUserTracker = KeyguardSliceProviderTest.this.mUserTracker;
+            mBgHandler =
+                    new Handler(TestableLooper.get(KeyguardSliceProviderTest.this).getLooper());
         }
 
         @Override
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 6f7c217..666978e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
@@ -205,6 +205,8 @@
         when(mStatusBarKeyguardViewManager.getViewRootImpl()).thenReturn(testViewRoot);
         when(mDreamingToLockscreenTransitionViewModel.getDreamOverlayAlpha())
                 .thenReturn(mock(Flow.class));
+        when(mDreamingToLockscreenTransitionViewModel.getTransitionEnded())
+                .thenReturn(mock(Flow.class));
         mNotificationShadeWindowController = new NotificationShadeWindowControllerImpl(mContext,
                 mWindowManager, mActivityManager, mDozeParameters, mStatusBarStateController,
                 mConfigurationController, mViewMediator, mKeyguardBypassController,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ResourceTrimmerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ResourceTrimmerTest.kt
index b4bd473..925ac30 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ResourceTrimmerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ResourceTrimmerTest.kt
@@ -10,7 +10,7 @@
 import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
 import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
 import com.android.systemui.keyguard.domain.interactor.KeyguardInteractorFactory
-import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractorFactory
 import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.keyguard.shared.model.TransitionStep
 import com.android.systemui.keyguard.shared.model.WakeSleepReason
@@ -67,10 +67,11 @@
         resourceTrimmer =
             ResourceTrimmer(
                 keyguardInteractor,
-                KeyguardTransitionInteractor(
-                    keyguardTransitionRepository,
-                    testScope.backgroundScope
-                ),
+                KeyguardTransitionInteractorFactory.create(
+                        scope = TestScope().backgroundScope,
+                        repository = keyguardTransitionRepository,
+                    )
+                    .keyguardTransitionInteractor,
                 globalWindowManager,
                 testScope.backgroundScope,
                 testDispatcher,
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 e042564..d62db5d 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
@@ -49,7 +49,7 @@
 import com.android.systemui.flags.Flags.FACE_AUTH_REFACTOR
 import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
 import com.android.systemui.keyguard.domain.interactor.KeyguardInteractorFactory
-import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractorFactory
 import com.android.systemui.keyguard.shared.model.AuthenticationStatus
 import com.android.systemui.keyguard.shared.model.DetectionStatus
 import com.android.systemui.keyguard.shared.model.ErrorAuthenticationStatus
@@ -216,7 +216,11 @@
             )
         keyguardTransitionRepository = FakeKeyguardTransitionRepository()
         val keyguardTransitionInteractor =
-            KeyguardTransitionInteractor(keyguardTransitionRepository, testScope.backgroundScope)
+            KeyguardTransitionInteractorFactory.create(
+                    scope = TestScope().backgroundScope,
+                    repository = keyguardTransitionRepository,
+                )
+                .keyguardTransitionInteractor
         return DeviceEntryFaceAuthRepositoryImpl(
             mContext,
             fmOverride,
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 ee5c1cc3..3e81cd3 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
@@ -89,7 +89,11 @@
         faceAuthRepository = FakeDeviceEntryFaceAuthRepository()
         keyguardTransitionRepository = FakeKeyguardTransitionRepository()
         keyguardTransitionInteractor =
-            KeyguardTransitionInteractor(keyguardTransitionRepository, testScope.backgroundScope)
+            KeyguardTransitionInteractorFactory.create(
+                    scope = TestScope().backgroundScope,
+                    repository = keyguardTransitionRepository,
+                )
+                .keyguardTransitionInteractor
 
         underTest =
             SystemUIKeyguardFaceAuthInteractor(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardLongPressInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardLongPressInteractorTest.kt
index e9c22f9..0050d64 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardLongPressInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardLongPressInteractorTest.kt
@@ -292,10 +292,11 @@
                 appContext = mContext,
                 scope = testScope.backgroundScope,
                 transitionInteractor =
-                    KeyguardTransitionInteractor(
-                        keyguardTransitionRepository,
-                        testScope.backgroundScope
-                    ),
+                    KeyguardTransitionInteractorFactory.create(
+                            scope = testScope.backgroundScope,
+                            repository = keyguardTransitionRepository,
+                        )
+                        .keyguardTransitionInteractor,
                 repository = keyguardRepository,
                 logger = logger,
                 featureFlags =
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorTest.kt
index fa4941c..9e9c25e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorTest.kt
@@ -17,9 +17,9 @@
 
 package com.android.systemui.keyguard.domain.interactor
 
-import com.android.systemui.RoboPilotTest
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
+import com.android.systemui.RoboPilotTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.coroutines.collectValues
 import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
@@ -54,7 +54,10 @@
     @Before
     fun setUp() {
         repository = FakeKeyguardTransitionRepository()
-        underTest = KeyguardTransitionInteractor(repository, testScope.backgroundScope)
+        underTest = KeyguardTransitionInteractorFactory.create(
+                scope = testScope.backgroundScope,
+                repository = repository,
+        ).keyguardTransitionInteractor
     }
 
     @Test
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 b559015..d01a46e 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
@@ -19,6 +19,7 @@
 import androidx.test.filters.SmallTest
 import com.android.keyguard.KeyguardSecurityModel
 import com.android.keyguard.KeyguardSecurityModel.SecurityMode.PIN
+import com.android.keyguard.TestScopeProvider
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.bouncer.data.repository.FakeKeyguardBouncerRepository
 import com.android.systemui.flags.FakeFeatureFlags
@@ -92,76 +93,97 @@
     @Before
     fun setUp() {
         MockitoAnnotations.initMocks(this)
-        testScope = TestScope()
+        testScope = TestScopeProvider.getTestScope()
 
         keyguardRepository = FakeKeyguardRepository()
         bouncerRepository = FakeKeyguardBouncerRepository()
         shadeRepository = FakeShadeRepository()
         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(
-                scope = testScope,
-                keyguardInteractor = createKeyguardInteractor(),
-                transitionRepository = transitionRepository,
-                transitionInteractor = transitionInteractor,
-                shadeRepository = shadeRepository,
-        ).apply { start() }
+        transitionInteractor =
+            KeyguardTransitionInteractorFactory.create(
+                    scope = testScope,
+                    repository = transitionRepository,
+                )
+                .keyguardTransitionInteractor
 
-        fromPrimaryBouncerTransitionInteractor = FromPrimaryBouncerTransitionInteractor(
-                scope = testScope,
-                keyguardInteractor = createKeyguardInteractor(),
-                transitionRepository = transitionRepository,
-                transitionInteractor = transitionInteractor,
-                keyguardSecurityModel = keyguardSecurityModel,
-        ).apply { start() }
+        fromLockscreenTransitionInteractor =
+            FromLockscreenTransitionInteractor(
+                    scope = testScope,
+                    keyguardInteractor = createKeyguardInteractor(),
+                    transitionRepository = transitionRepository,
+                    transitionInteractor = transitionInteractor,
+                    shadeRepository = shadeRepository,
+                )
+                .apply { start() }
 
-        fromDreamingTransitionInteractor = FromDreamingTransitionInteractor(
-                scope = testScope,
-                keyguardInteractor = createKeyguardInteractor(),
-                transitionRepository = transitionRepository,
-                transitionInteractor = transitionInteractor,
-        ).apply { start() }
+        fromPrimaryBouncerTransitionInteractor =
+            FromPrimaryBouncerTransitionInteractor(
+                    scope = testScope,
+                    keyguardInteractor = createKeyguardInteractor(),
+                    transitionRepository = transitionRepository,
+                    transitionInteractor = transitionInteractor,
+                    keyguardSecurityModel = keyguardSecurityModel,
+                )
+                .apply { start() }
 
-        fromAodTransitionInteractor = FromAodTransitionInteractor(
-                scope = testScope,
-                keyguardInteractor = createKeyguardInteractor(),
-                transitionRepository = transitionRepository,
-                transitionInteractor = transitionInteractor,
-        ).apply { start() }
+        fromDreamingTransitionInteractor =
+            FromDreamingTransitionInteractor(
+                    scope = testScope,
+                    keyguardInteractor = createKeyguardInteractor(),
+                    transitionRepository = transitionRepository,
+                    transitionInteractor = transitionInteractor,
+                )
+                .apply { start() }
 
-        fromGoneTransitionInteractor = FromGoneTransitionInteractor(
-                scope = testScope,
-                keyguardInteractor = createKeyguardInteractor(),
-                transitionRepository = transitionRepository,
-                transitionInteractor = transitionInteractor,
-        ).apply { start() }
+        fromAodTransitionInteractor =
+            FromAodTransitionInteractor(
+                    scope = testScope,
+                    keyguardInteractor = createKeyguardInteractor(),
+                    transitionRepository = transitionRepository,
+                    transitionInteractor = transitionInteractor,
+                )
+                .apply { start() }
 
-        fromDozingTransitionInteractor = FromDozingTransitionInteractor(
-                scope = testScope,
-                keyguardInteractor = createKeyguardInteractor(),
-                transitionRepository = transitionRepository,
-                transitionInteractor = transitionInteractor,
-        ).apply { start() }
+        fromGoneTransitionInteractor =
+            FromGoneTransitionInteractor(
+                    scope = testScope,
+                    keyguardInteractor = createKeyguardInteractor(),
+                    transitionRepository = transitionRepository,
+                    transitionInteractor = transitionInteractor,
+                )
+                .apply { start() }
 
-        fromOccludedTransitionInteractor = FromOccludedTransitionInteractor(
-                scope = testScope,
-                keyguardInteractor = createKeyguardInteractor(),
-                transitionRepository = transitionRepository,
-                transitionInteractor = transitionInteractor,
-        ).apply { start() }
+        fromDozingTransitionInteractor =
+            FromDozingTransitionInteractor(
+                    scope = testScope,
+                    keyguardInteractor = createKeyguardInteractor(),
+                    transitionRepository = transitionRepository,
+                    transitionInteractor = transitionInteractor,
+                )
+                .apply { start() }
 
-        fromAlternateBouncerTransitionInteractor = FromAlternateBouncerTransitionInteractor(
-                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
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/LightRevealScrimInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/LightRevealScrimInteractorTest.kt
index 08e99dc..6e7ba6d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/LightRevealScrimInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/LightRevealScrimInteractorTest.kt
@@ -46,7 +46,11 @@
     private val fakeLightRevealScrimRepository = FakeLightRevealScrimRepository()
 
     private val keyguardTransitionInteractor =
-        KeyguardTransitionInteractor(fakeKeyguardTransitionRepository, TestScope().backgroundScope)
+        KeyguardTransitionInteractorFactory.create(
+                scope = TestScope().backgroundScope,
+                repository = fakeKeyguardTransitionRepository,
+            )
+            .keyguardTransitionInteractor
 
     private lateinit var underTest: LightRevealScrimInteractor
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/UdfpsKeyguardInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/UdfpsKeyguardInteractorTest.kt
new file mode 100644
index 0000000..1baca21
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/UdfpsKeyguardInteractorTest.kt
@@ -0,0 +1,168 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package com.android.systemui.keyguard.domain.interactor
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.bouncer.data.repository.FakeKeyguardBouncerRepository
+import com.android.systemui.bouncer.data.repository.KeyguardBouncerRepository
+import com.android.systemui.common.ui.data.repository.FakeConfigurationRepository
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.doze.util.BurnInHelperWrapper
+import com.android.systemui.flags.FakeFeatureFlags
+import com.android.systemui.flags.Flags
+import com.android.systemui.keyguard.data.repository.FakeCommandQueue
+import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
+import com.android.systemui.keyguard.shared.model.StatusBarState
+import com.android.systemui.keyguard.shared.model.WakeSleepReason
+import com.android.systemui.keyguard.shared.model.WakefulnessModel
+import com.android.systemui.keyguard.shared.model.WakefulnessState
+import com.android.systemui.util.mockito.eq
+import com.android.systemui.util.mockito.whenever
+import com.android.systemui.util.time.FakeSystemClock
+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.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.anyInt
+import org.mockito.Mock
+import org.mockito.MockitoAnnotations
+
+@ExperimentalCoroutinesApi
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class UdfpsKeyguardInteractorTest : SysuiTestCase() {
+    private val burnInProgress = 1f
+    private val burnInYOffset = 20
+    private val burnInXOffset = 10
+
+    private lateinit var testScope: TestScope
+    private lateinit var configRepository: FakeConfigurationRepository
+    private lateinit var bouncerRepository: KeyguardBouncerRepository
+    private lateinit var keyguardRepository: FakeKeyguardRepository
+    private lateinit var fakeCommandQueue: FakeCommandQueue
+    private lateinit var featureFlags: FakeFeatureFlags
+    private lateinit var burnInInteractor: BurnInInteractor
+
+    @Mock private lateinit var burnInHelper: BurnInHelperWrapper
+
+    private lateinit var underTest: UdfpsKeyguardInteractor
+
+    @Before
+    fun setUp() {
+        MockitoAnnotations.initMocks(this)
+
+        testScope = TestScope()
+        configRepository = FakeConfigurationRepository()
+        keyguardRepository = FakeKeyguardRepository()
+        bouncerRepository = FakeKeyguardBouncerRepository()
+        fakeCommandQueue = FakeCommandQueue()
+        featureFlags =
+            FakeFeatureFlags().apply {
+                set(Flags.REFACTOR_UDFPS_KEYGUARD_VIEWS, true)
+                set(Flags.FACE_AUTH_REFACTOR, false)
+            }
+        burnInInteractor =
+            BurnInInteractor(
+                context,
+                burnInHelper,
+                testScope.backgroundScope,
+                configRepository,
+                FakeSystemClock(),
+            )
+
+        underTest =
+            UdfpsKeyguardInteractor(
+                configRepository,
+                burnInInteractor,
+                KeyguardInteractor(
+                    keyguardRepository,
+                    fakeCommandQueue,
+                    featureFlags,
+                    bouncerRepository,
+                    configRepository,
+                ),
+            )
+    }
+
+    @Test
+    fun dozeChanges_updatesUdfpsAodModel() =
+        testScope.runTest {
+            val burnInOffsets by collectLastValue(underTest.burnInOffsets)
+            initializeBurnInOffsets()
+
+            // WHEN we're not dozing
+            setAwake()
+            runCurrent()
+
+            // THEN burn in offsets are 0
+            assertThat(burnInOffsets?.burnInProgress).isEqualTo(0f)
+            assertThat(burnInOffsets?.burnInYOffset).isEqualTo(0)
+            assertThat(burnInOffsets?.burnInXOffset).isEqualTo(0)
+
+            // WHEN we're in the middle of the doze amount change
+            keyguardRepository.setDozeAmount(.50f)
+            runCurrent()
+
+            // THEN burn in is updated (between 0 and the full offset)
+            assertThat(burnInOffsets?.burnInProgress).isGreaterThan(0f)
+            assertThat(burnInOffsets?.burnInYOffset).isGreaterThan(0)
+            assertThat(burnInOffsets?.burnInXOffset).isGreaterThan(0)
+            assertThat(burnInOffsets?.burnInProgress).isLessThan(burnInProgress)
+            assertThat(burnInOffsets?.burnInYOffset).isLessThan(burnInYOffset)
+            assertThat(burnInOffsets?.burnInXOffset).isLessThan(burnInXOffset)
+
+            // WHEN we're fully dozing
+            keyguardRepository.setDozeAmount(1f)
+            runCurrent()
+
+            // THEN burn in offsets are updated to final current values (for the given time)
+            assertThat(burnInOffsets?.burnInProgress).isEqualTo(burnInProgress)
+            assertThat(burnInOffsets?.burnInYOffset).isEqualTo(burnInYOffset)
+            assertThat(burnInOffsets?.burnInXOffset).isEqualTo(burnInXOffset)
+        }
+
+    private fun initializeBurnInOffsets() {
+        whenever(burnInHelper.burnInProgressOffset()).thenReturn(burnInProgress)
+        whenever(burnInHelper.burnInOffset(anyInt(), /* xAxis */ eq(true)))
+            .thenReturn(burnInXOffset)
+        whenever(burnInHelper.burnInOffset(anyInt(), /* xAxis */ eq(false)))
+            .thenReturn(burnInYOffset)
+    }
+
+    private fun setAwake() {
+        keyguardRepository.setDozeAmount(0f)
+        burnInInteractor.dozeTimeTick()
+
+        bouncerRepository.setAlternateVisible(false)
+        keyguardRepository.setStatusBarState(StatusBarState.KEYGUARD)
+        bouncerRepository.setPrimaryShow(false)
+        keyguardRepository.setWakefulnessModel(
+            WakefulnessModel(
+                WakefulnessState.AWAKE,
+                WakeSleepReason.POWER_BUTTON,
+                WakeSleepReason.POWER_BUTTON,
+            )
+        )
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/DefaultLockscreenLayoutTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/DefaultLockscreenLayoutTest.kt
new file mode 100644
index 0000000..2e97208
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/DefaultLockscreenLayoutTest.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.keyguard.ui.view.layout
+
+import android.graphics.Point
+import android.view.ViewGroup
+import android.view.WindowManager
+import androidx.constraintlayout.widget.ConstraintSet
+import androidx.test.filters.SmallTest
+import com.android.keyguard.KeyguardUpdateMonitor
+import com.android.systemui.R
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.biometrics.AuthController
+import com.android.systemui.keyguard.ui.view.KeyguardRootView
+import com.android.systemui.monet.utils.ArgbSubject.assertThat
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import org.mockito.Answers
+import org.mockito.Mock
+import org.mockito.MockitoAnnotations
+
+@RunWith(JUnit4::class)
+@SmallTest
+class DefaultLockscreenLayoutTest : SysuiTestCase() {
+    private lateinit var defaultLockscreenLayout: DefaultLockscreenLayout
+    private lateinit var rootView: KeyguardRootView
+    @Mock private lateinit var authController: AuthController
+    @Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
+    @Mock(answer = Answers.RETURNS_DEEP_STUBS) private lateinit var windowManager: WindowManager
+
+    @Before
+    fun setup() {
+        MockitoAnnotations.initMocks(this)
+        rootView = KeyguardRootView(context, null)
+        defaultLockscreenLayout =
+            DefaultLockscreenLayout(authController, keyguardUpdateMonitor, windowManager, context)
+    }
+
+    @Test
+    fun testLayoutViews_KeyguardIndicationArea() {
+        defaultLockscreenLayout.layoutViews(rootView)
+        val constraint = getViewConstraint(R.id.keyguard_indication_area)
+        assertThat(constraint.layout.bottomToBottom).isEqualTo(ConstraintSet.PARENT_ID)
+        assertThat(constraint.layout.startToStart).isEqualTo(ConstraintSet.PARENT_ID)
+        assertThat(constraint.layout.endToEnd).isEqualTo(ConstraintSet.PARENT_ID)
+        assertThat(constraint.layout.mWidth).isEqualTo(ViewGroup.LayoutParams.MATCH_PARENT)
+        assertThat(constraint.layout.mHeight).isEqualTo(ViewGroup.LayoutParams.WRAP_CONTENT)
+    }
+
+    @Test
+    fun testLayoutViews_lockIconView() {
+        defaultLockscreenLayout.layoutViews(rootView)
+        val constraint = getViewConstraint(R.id.lock_icon_view)
+        assertThat(constraint.layout.topToTop).isEqualTo(ConstraintSet.PARENT_ID)
+        assertThat(constraint.layout.startToStart).isEqualTo(ConstraintSet.PARENT_ID)
+    }
+
+    @Test
+    fun testCenterLockIcon() {
+        defaultLockscreenLayout.centerLockIcon(Point(5, 6), 1F, 5, rootView)
+        val constraint = getViewConstraint(R.id.lock_icon_view)
+
+        assertThat(constraint.layout.mWidth).isEqualTo(2)
+        assertThat(constraint.layout.mHeight).isEqualTo(2)
+        assertThat(constraint.layout.topToTop).isEqualTo(ConstraintSet.PARENT_ID)
+        assertThat(constraint.layout.startToStart).isEqualTo(ConstraintSet.PARENT_ID)
+        assertThat(constraint.layout.topMargin).isEqualTo(5)
+        assertThat(constraint.layout.startMargin).isEqualTo(4)
+    }
+
+    /** Get the ConstraintLayout constraint of the view. */
+    private fun getViewConstraint(viewId: Int): ConstraintSet.Constraint {
+        val constraintSet = ConstraintSet()
+        constraintSet.clone(rootView)
+        return constraintSet.getConstraint(viewId)
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/KeyguardLayoutManagerCommandListenerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/KeyguardLayoutManagerCommandListenerTest.kt
new file mode 100644
index 0000000..145b2fd
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/KeyguardLayoutManagerCommandListenerTest.kt
@@ -0,0 +1,88 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.ui.view.layout
+
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.statusbar.commandline.Command
+import com.android.systemui.statusbar.commandline.CommandRegistry
+import com.android.systemui.util.mockito.eq
+import com.android.systemui.util.mockito.whenever
+import com.android.systemui.util.mockito.withArgCaptor
+import java.io.PrintWriter
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import org.mockito.ArgumentMatchers.anyString
+import org.mockito.Mock
+import org.mockito.Mockito.atLeastOnce
+import org.mockito.Mockito.mock
+import org.mockito.Mockito.never
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+@RunWith(JUnit4::class)
+@SmallTest
+class KeyguardLayoutManagerCommandListenerTest : SysuiTestCase() {
+    private lateinit var keyguardLayoutManagerCommandListener: KeyguardLayoutManagerCommandListener
+    @Mock private lateinit var commandRegistry: CommandRegistry
+    @Mock private lateinit var keyguardLayoutManager: KeyguardLayoutManager
+    @Mock private lateinit var pw: PrintWriter
+    private lateinit var command: () -> Command
+
+    @Before
+    fun setup() {
+        MockitoAnnotations.initMocks(this)
+        keyguardLayoutManagerCommandListener =
+            KeyguardLayoutManagerCommandListener(
+                commandRegistry,
+                keyguardLayoutManager,
+            )
+        keyguardLayoutManagerCommandListener.start()
+        command =
+            withArgCaptor<() -> Command> {
+                verify(commandRegistry).registerCommand(eq("layout"), capture())
+            }
+    }
+
+    @Test
+    fun testHelp() {
+        command().execute(pw, listOf("help"))
+        verify(pw, atLeastOnce()).println(anyString())
+        verify(keyguardLayoutManager, never()).transitionToLayout(anyString())
+    }
+
+    @Test
+    fun testBlank() {
+        command().execute(pw, listOf())
+        verify(pw, atLeastOnce()).println(anyString())
+        verify(keyguardLayoutManager, never()).transitionToLayout(anyString())
+    }
+
+    @Test
+    fun testValidArg() {
+        bindFakeIdMapToLayoutManager()
+        command().execute(pw, listOf("fake"))
+        verify(keyguardLayoutManager).transitionToLayout("fake")
+    }
+
+    private fun bindFakeIdMapToLayoutManager() {
+        val map = mapOf("fake" to mock(LockscreenLayout::class.java))
+        whenever(keyguardLayoutManager.layoutIdMap).thenReturn(map)
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/KeyguardLayoutManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/KeyguardLayoutManagerTest.kt
new file mode 100644
index 0000000..95b2030
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/KeyguardLayoutManagerTest.kt
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package com.android.systemui.keyguard.ui.view.layout
+
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.keyguard.ui.view.KeyguardRootView
+import com.android.systemui.keyguard.ui.view.layout.DefaultLockscreenLayout.Companion.DEFAULT
+import com.android.systemui.statusbar.policy.ConfigurationController
+import com.android.systemui.util.mockito.whenever
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import org.mockito.Mock
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+@RunWith(JUnit4::class)
+@SmallTest
+class KeyguardLayoutManagerTest : SysuiTestCase() {
+    private lateinit var keyguardLayoutManager: KeyguardLayoutManager
+    @Mock lateinit var configurationController: ConfigurationController
+    @Mock lateinit var defaultLockscreenLayout: DefaultLockscreenLayout
+    @Mock lateinit var keyguardRootView: KeyguardRootView
+
+    @Before
+    fun setup() {
+        MockitoAnnotations.initMocks(this)
+        whenever(defaultLockscreenLayout.id).thenReturn(DEFAULT)
+        keyguardLayoutManager =
+            KeyguardLayoutManager(
+                configurationController,
+                setOf(defaultLockscreenLayout),
+                keyguardRootView
+            )
+    }
+
+    @Test
+    fun testDefaultLayout() {
+        keyguardLayoutManager.transitionToLayout(DEFAULT)
+        verify(defaultLockscreenLayout).layoutViews(keyguardRootView)
+    }
+
+    @Test
+    fun testTransitionToLayout_validId() {
+        assertThat(keyguardLayoutManager.transitionToLayout(DEFAULT)).isTrue()
+    }
+    @Test
+    fun testTransitionToLayout_invalidId() {
+        assertThat(keyguardLayoutManager.transitionToLayout("abc")).isFalse()
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModelTest.kt
index a341346..c67f535 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModelTest.kt
@@ -22,8 +22,18 @@
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
 import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractorFactory
 import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.KeyguardState.AOD
+import com.android.systemui.keyguard.shared.model.KeyguardState.DOZING
+import com.android.systemui.keyguard.shared.model.KeyguardState.DREAMING
+import com.android.systemui.keyguard.shared.model.KeyguardState.GONE
+import com.android.systemui.keyguard.shared.model.KeyguardState.LOCKSCREEN
 import com.android.systemui.keyguard.shared.model.TransitionState
+import com.android.systemui.keyguard.shared.model.TransitionState.CANCELED
+import com.android.systemui.keyguard.shared.model.TransitionState.FINISHED
+import com.android.systemui.keyguard.shared.model.TransitionState.RUNNING
+import com.android.systemui.keyguard.shared.model.TransitionState.STARTED
 import com.android.systemui.keyguard.shared.model.TransitionStep
 import com.android.systemui.util.mockito.mock
 import com.google.common.collect.Range
@@ -47,7 +57,12 @@
     @Before
     fun setUp() {
         repository = FakeKeyguardTransitionRepository()
-        val interactor = KeyguardTransitionInteractor(repository, TestScope().backgroundScope)
+        val interactor =
+            KeyguardTransitionInteractorFactory.create(
+                    scope = TestScope().backgroundScope,
+                    repository = repository,
+                )
+                .keyguardTransitionInteractor
         underTest = DreamingToLockscreenTransitionViewModel(interactor, mock())
     }
 
@@ -60,7 +75,7 @@
             val job =
                 underTest.dreamOverlayTranslationY(pixels).onEach { values.add(it) }.launchIn(this)
 
-            repository.sendTransitionStep(step(0f, TransitionState.STARTED))
+            repository.sendTransitionStep(step(0f, STARTED))
             repository.sendTransitionStep(step(0f))
             repository.sendTransitionStep(step(0.3f))
             repository.sendTransitionStep(step(0.5f))
@@ -82,7 +97,7 @@
             val job = underTest.dreamOverlayAlpha.onEach { values.add(it) }.launchIn(this)
 
             // Should start running here...
-            repository.sendTransitionStep(step(0f, TransitionState.STARTED))
+            repository.sendTransitionStep(step(0f, STARTED))
             repository.sendTransitionStep(step(0f))
             repository.sendTransitionStep(step(0.1f))
             repository.sendTransitionStep(step(0.5f))
@@ -104,7 +119,7 @@
 
             val job = underTest.lockscreenAlpha.onEach { values.add(it) }.launchIn(this)
 
-            repository.sendTransitionStep(step(0f, TransitionState.STARTED))
+            repository.sendTransitionStep(step(0f, STARTED))
             repository.sendTransitionStep(step(0f))
             repository.sendTransitionStep(step(0.1f))
             repository.sendTransitionStep(step(0.2f))
@@ -126,7 +141,7 @@
             val job =
                 underTest.lockscreenTranslationY(pixels).onEach { values.add(it) }.launchIn(this)
 
-            repository.sendTransitionStep(step(0f, TransitionState.STARTED))
+            repository.sendTransitionStep(step(0f, STARTED))
             repository.sendTransitionStep(step(0f))
             repository.sendTransitionStep(step(0.3f))
             repository.sendTransitionStep(step(0.5f))
@@ -138,13 +153,44 @@
             job.cancel()
         }
 
-    private fun step(
-        value: Float,
-        state: TransitionState = TransitionState.RUNNING
-    ): TransitionStep {
+    @Test
+    fun transitionEnded() =
+        runTest(UnconfinedTestDispatcher()) {
+            val values = mutableListOf<TransitionStep>()
+
+            val job = underTest.transitionEnded.onEach { values.add(it) }.launchIn(this)
+
+            repository.sendTransitionStep(TransitionStep(DOZING, DREAMING, 0.0f, STARTED))
+            repository.sendTransitionStep(TransitionStep(DOZING, DREAMING, 1.0f, FINISHED))
+
+            repository.sendTransitionStep(TransitionStep(DREAMING, LOCKSCREEN, 0.0f, STARTED))
+            repository.sendTransitionStep(TransitionStep(DREAMING, LOCKSCREEN, 0.1f, RUNNING))
+            repository.sendTransitionStep(TransitionStep(DREAMING, LOCKSCREEN, 1.0f, FINISHED))
+
+            repository.sendTransitionStep(TransitionStep(LOCKSCREEN, DREAMING, 0.0f, STARTED))
+            repository.sendTransitionStep(TransitionStep(LOCKSCREEN, DREAMING, 0.5f, RUNNING))
+            repository.sendTransitionStep(TransitionStep(LOCKSCREEN, DREAMING, 1.0f, FINISHED))
+
+            repository.sendTransitionStep(TransitionStep(DREAMING, GONE, 0.0f, STARTED))
+            repository.sendTransitionStep(TransitionStep(DREAMING, GONE, 0.5f, RUNNING))
+            repository.sendTransitionStep(TransitionStep(DREAMING, GONE, 1.0f, CANCELED))
+
+            repository.sendTransitionStep(TransitionStep(DREAMING, AOD, 0.0f, STARTED))
+            repository.sendTransitionStep(TransitionStep(DREAMING, AOD, 1.0f, FINISHED))
+
+            assertThat(values.size).isEqualTo(3)
+            values.forEach {
+                assertThat(it.transitionState == FINISHED || it.transitionState == CANCELED)
+                    .isTrue()
+            }
+
+            job.cancel()
+        }
+
+    private fun step(value: Float, state: TransitionState = RUNNING): TransitionStep {
         return TransitionStep(
-            from = KeyguardState.DREAMING,
-            to = KeyguardState.LOCKSCREEN,
+            from = DREAMING,
+            to = LOCKSCREEN,
             value = value,
             transitionState = state,
             ownerName = "DreamingToLockscreenTransitionViewModelTest"
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/GoneToDreamingTransitionViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/GoneToDreamingTransitionViewModelTest.kt
index 694539b..75c8bff 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/GoneToDreamingTransitionViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/GoneToDreamingTransitionViewModelTest.kt
@@ -21,7 +21,7 @@
 import com.android.systemui.RoboPilotTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
-import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractorFactory
 import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.keyguard.shared.model.TransitionState
 import com.android.systemui.keyguard.shared.model.TransitionStep
@@ -46,7 +46,12 @@
     @Before
     fun setUp() {
         repository = FakeKeyguardTransitionRepository()
-        val interactor = KeyguardTransitionInteractor(repository, TestScope().backgroundScope)
+        val interactor =
+            KeyguardTransitionInteractorFactory.create(
+                    scope = TestScope().backgroundScope,
+                    repository = repository,
+                )
+                .keyguardTransitionInteractor
         underTest = GoneToDreamingTransitionViewModel(interactor)
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt
index 29886d5..d02b3fc 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt
@@ -41,13 +41,12 @@
 import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceRemoteUserSelectionManager
 import com.android.systemui.keyguard.data.repository.FakeBiometricSettingsRepository
 import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
-import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
 import com.android.systemui.keyguard.data.repository.KeyguardQuickAffordanceRepository
 import com.android.systemui.keyguard.domain.interactor.KeyguardBottomAreaInteractor
 import com.android.systemui.keyguard.domain.interactor.KeyguardInteractorFactory
 import com.android.systemui.keyguard.domain.interactor.KeyguardLongPressInteractor
 import com.android.systemui.keyguard.domain.interactor.KeyguardQuickAffordanceInteractor
-import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractorFactory
 import com.android.systemui.keyguard.domain.quickaffordance.FakeKeyguardQuickAffordanceRegistry
 import com.android.systemui.keyguard.shared.quickaffordance.ActivationState
 import com.android.systemui.keyguard.shared.quickaffordance.KeyguardQuickAffordancePosition
@@ -210,10 +209,10 @@
                 appContext = mContext,
                 scope = testScope.backgroundScope,
                 transitionInteractor =
-                    KeyguardTransitionInteractor(
-                        repository = FakeKeyguardTransitionRepository(),
-                        scope = testScope.backgroundScope
-                    ),
+                    KeyguardTransitionInteractorFactory.create(
+                            scope = TestScope().backgroundScope,
+                        )
+                        .keyguardTransitionInteractor,
                 repository = repository,
                 logger = UiEventLoggerFake(),
                 featureFlags = featureFlags,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDreamingTransitionViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDreamingTransitionViewModelTest.kt
index ea17751..12fe07f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDreamingTransitionViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDreamingTransitionViewModelTest.kt
@@ -21,7 +21,7 @@
 import com.android.systemui.RoboPilotTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
-import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractorFactory
 import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.keyguard.shared.model.TransitionState
 import com.android.systemui.keyguard.shared.model.TransitionStep
@@ -46,7 +46,12 @@
     @Before
     fun setUp() {
         repository = FakeKeyguardTransitionRepository()
-        val interactor = KeyguardTransitionInteractor(repository, TestScope().backgroundScope)
+        val interactor =
+            KeyguardTransitionInteractorFactory.create(
+                    scope = TestScope().backgroundScope,
+                    repository = repository,
+                )
+                .keyguardTransitionInteractor
         underTest = LockscreenToDreamingTransitionViewModel(interactor)
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToOccludedTransitionViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToOccludedTransitionViewModelTest.kt
index bf56a98..83ae631 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToOccludedTransitionViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToOccludedTransitionViewModelTest.kt
@@ -21,7 +21,7 @@
 import com.android.systemui.RoboPilotTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
-import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractorFactory
 import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.keyguard.shared.model.TransitionState
 import com.android.systemui.keyguard.shared.model.TransitionStep
@@ -46,7 +46,12 @@
     @Before
     fun setUp() {
         repository = FakeKeyguardTransitionRepository()
-        val interactor = KeyguardTransitionInteractor(repository, TestScope().backgroundScope)
+        val interactor =
+            KeyguardTransitionInteractorFactory.create(
+                    scope = TestScope().backgroundScope,
+                    repository = repository,
+                )
+                .keyguardTransitionInteractor
         underTest = LockscreenToOccludedTransitionViewModel(interactor)
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToLockscreenTransitionViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToLockscreenTransitionViewModelTest.kt
index 34da26e..8860399 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToLockscreenTransitionViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToLockscreenTransitionViewModelTest.kt
@@ -21,7 +21,7 @@
 import com.android.systemui.RoboPilotTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
-import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractorFactory
 import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.keyguard.shared.model.TransitionState
 import com.android.systemui.keyguard.shared.model.TransitionStep
@@ -46,7 +46,12 @@
     @Before
     fun setUp() {
         repository = FakeKeyguardTransitionRepository()
-        val interactor = KeyguardTransitionInteractor(repository, TestScope().backgroundScope)
+        val interactor =
+            KeyguardTransitionInteractorFactory.create(
+                    scope = TestScope().backgroundScope,
+                    repository = repository,
+                )
+                .keyguardTransitionInteractor
         underTest = OccludedToLockscreenTransitionViewModel(interactor)
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModelTest.kt
index f88b71d..d8c78eb 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModelTest.kt
@@ -22,7 +22,7 @@
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor
 import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
-import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractorFactory
 import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.keyguard.shared.model.ScrimAlpha
 import com.android.systemui.keyguard.shared.model.TransitionState
@@ -55,7 +55,12 @@
     fun setUp() {
         MockitoAnnotations.initMocks(this)
         repository = FakeKeyguardTransitionRepository()
-        val interactor = KeyguardTransitionInteractor(repository, TestScope().backgroundScope)
+        val interactor =
+            KeyguardTransitionInteractorFactory.create(
+                    scope = TestScope().backgroundScope,
+                    repository = repository,
+                )
+                .keyguardTransitionInteractor
         underTest =
             PrimaryBouncerToGoneTransitionViewModel(
                 interactor,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsAodViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsAodViewModelTest.kt
new file mode 100644
index 0000000..436c09c
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsAodViewModelTest.kt
@@ -0,0 +1,149 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.ui.viewmodel
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.RoboPilotTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.bouncer.data.repository.FakeKeyguardBouncerRepository
+import com.android.systemui.bouncer.data.repository.KeyguardBouncerRepository
+import com.android.systemui.common.ui.data.repository.FakeConfigurationRepository
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.doze.util.BurnInHelperWrapper
+import com.android.systemui.flags.FakeFeatureFlags
+import com.android.systemui.flags.Flags
+import com.android.systemui.keyguard.data.repository.FakeCommandQueue
+import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
+import com.android.systemui.keyguard.domain.interactor.BurnInInteractor
+import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
+import com.android.systemui.keyguard.domain.interactor.UdfpsKeyguardInteractor
+import com.android.systemui.util.time.FakeSystemClock
+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.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.MockitoAnnotations
+
+@ExperimentalCoroutinesApi
+@SmallTest
+@RoboPilotTest
+@RunWith(AndroidJUnit4::class)
+class UdfpsAodViewModelTest : SysuiTestCase() {
+    private val defaultPadding = 12
+    private lateinit var underTest: UdfpsAodViewModel
+
+    private lateinit var testScope: TestScope
+    private lateinit var configRepository: FakeConfigurationRepository
+    private lateinit var bouncerRepository: KeyguardBouncerRepository
+    private lateinit var keyguardRepository: FakeKeyguardRepository
+    private lateinit var fakeCommandQueue: FakeCommandQueue
+    private lateinit var featureFlags: FakeFeatureFlags
+
+    @Mock private lateinit var burnInHelper: BurnInHelperWrapper
+
+    @Before
+    fun setUp() {
+        MockitoAnnotations.initMocks(this)
+        overrideResource(com.android.systemui.R.dimen.lock_icon_padding, defaultPadding)
+        testScope = TestScope()
+        configRepository = FakeConfigurationRepository()
+        keyguardRepository = FakeKeyguardRepository()
+        bouncerRepository = FakeKeyguardBouncerRepository()
+        fakeCommandQueue = FakeCommandQueue()
+        featureFlags =
+            FakeFeatureFlags().apply {
+                set(Flags.REFACTOR_UDFPS_KEYGUARD_VIEWS, true)
+                set(Flags.FACE_AUTH_REFACTOR, false)
+            }
+
+        val udfpsKeyguardInteractor =
+            UdfpsKeyguardInteractor(
+                configRepository,
+                BurnInInteractor(
+                    context,
+                    burnInHelper,
+                    testScope.backgroundScope,
+                    configRepository,
+                    FakeSystemClock(),
+                ),
+                KeyguardInteractor(
+                    keyguardRepository,
+                    fakeCommandQueue,
+                    featureFlags,
+                    bouncerRepository,
+                    configRepository,
+                ),
+            )
+
+        underTest =
+            UdfpsAodViewModel(
+                udfpsKeyguardInteractor,
+                context,
+            )
+    }
+
+    @Test
+    fun alphaAndVisibleUpdates_onDozeAmountChanges() =
+        testScope.runTest {
+            val alpha by collectLastValue(underTest.alpha)
+            val visible by collectLastValue(underTest.isVisible)
+
+            keyguardRepository.setDozeAmount(0f)
+            runCurrent()
+            assertThat(alpha).isEqualTo(0f)
+            assertThat(visible).isFalse()
+
+            keyguardRepository.setDozeAmount(.65f)
+            runCurrent()
+            assertThat(alpha).isEqualTo(.65f)
+            assertThat(visible).isTrue()
+
+            keyguardRepository.setDozeAmount(.23f)
+            runCurrent()
+            assertThat(alpha).isEqualTo(.23f)
+            assertThat(visible).isTrue()
+
+            keyguardRepository.setDozeAmount(1f)
+            runCurrent()
+            assertThat(alpha).isEqualTo(1f)
+            assertThat(visible).isTrue()
+        }
+
+    @Test
+    fun paddingUpdates_onScaleForResolutionChanges() =
+        testScope.runTest {
+            val padding by collectLastValue(underTest.padding)
+
+            configRepository.setScaleForResolution(1f)
+            runCurrent()
+            assertThat(padding).isEqualTo(defaultPadding)
+
+            configRepository.setScaleForResolution(2f)
+            runCurrent()
+            assertThat(padding).isEqualTo(defaultPadding * 2)
+
+            configRepository.setScaleForResolution(.5f)
+            runCurrent()
+            assertThat(padding).isEqualTo((defaultPadding * .5f).toInt())
+        }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsFingerprintViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsFingerprintViewModelTest.kt
new file mode 100644
index 0000000..a30e2a6
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsFingerprintViewModelTest.kt
@@ -0,0 +1,131 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.ui.viewmodel
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.bouncer.data.repository.FakeKeyguardBouncerRepository
+import com.android.systemui.bouncer.data.repository.KeyguardBouncerRepository
+import com.android.systemui.common.ui.data.repository.FakeConfigurationRepository
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.doze.util.BurnInHelperWrapper
+import com.android.systemui.flags.FakeFeatureFlags
+import com.android.systemui.flags.Flags
+import com.android.systemui.keyguard.data.repository.FakeCommandQueue
+import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
+import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
+import com.android.systemui.keyguard.domain.interactor.BurnInInteractor
+import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
+import com.android.systemui.keyguard.domain.interactor.UdfpsKeyguardInteractor
+import com.android.systemui.util.time.FakeSystemClock
+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.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.MockitoAnnotations
+
+/** Tests UdfpsFingerprintViewModel specific flows. */
+@ExperimentalCoroutinesApi
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class UdfpsFingerprintViewModelTest : SysuiTestCase() {
+    private val defaultPadding = 12
+    private lateinit var underTest: FingerprintViewModel
+
+    private lateinit var testScope: TestScope
+    private lateinit var configRepository: FakeConfigurationRepository
+    private lateinit var bouncerRepository: KeyguardBouncerRepository
+    private lateinit var keyguardRepository: FakeKeyguardRepository
+    private lateinit var fakeCommandQueue: FakeCommandQueue
+    private lateinit var featureFlags: FakeFeatureFlags
+    private lateinit var transitionRepository: FakeKeyguardTransitionRepository
+
+    @Mock private lateinit var burnInHelper: BurnInHelperWrapper
+
+    @Before
+    fun setUp() {
+        MockitoAnnotations.initMocks(this)
+        overrideResource(com.android.systemui.R.dimen.lock_icon_padding, defaultPadding)
+        testScope = TestScope()
+        configRepository = FakeConfigurationRepository()
+        keyguardRepository = FakeKeyguardRepository()
+        bouncerRepository = FakeKeyguardBouncerRepository()
+        fakeCommandQueue = FakeCommandQueue()
+        featureFlags =
+            FakeFeatureFlags().apply {
+                set(Flags.REFACTOR_UDFPS_KEYGUARD_VIEWS, true)
+                set(Flags.FACE_AUTH_REFACTOR, false)
+            }
+        bouncerRepository = FakeKeyguardBouncerRepository()
+        transitionRepository = FakeKeyguardTransitionRepository()
+        val transitionInteractor =
+            KeyguardTransitionInteractor(
+                transitionRepository,
+                testScope.backgroundScope,
+            )
+        val udfpsKeyguardInteractor =
+            UdfpsKeyguardInteractor(
+                configRepository,
+                BurnInInteractor(
+                    context,
+                    burnInHelper,
+                    testScope.backgroundScope,
+                    configRepository,
+                    FakeSystemClock(),
+                ),
+                KeyguardInteractor(
+                    keyguardRepository,
+                    fakeCommandQueue,
+                    featureFlags,
+                    bouncerRepository,
+                    configRepository,
+                ),
+            )
+
+        underTest =
+            FingerprintViewModel(
+                context,
+                transitionInteractor,
+                udfpsKeyguardInteractor,
+            )
+    }
+
+    @Test
+    fun paddingUpdates_onScaleForResolutionChanges() =
+        testScope.runTest {
+            val padding by collectLastValue(underTest.padding)
+
+            configRepository.setScaleForResolution(1f)
+            runCurrent()
+            assertThat(padding).isEqualTo(defaultPadding)
+
+            configRepository.setScaleForResolution(2f)
+            runCurrent()
+            assertThat(padding).isEqualTo(defaultPadding * 2)
+
+            configRepository.setScaleForResolution(.5f)
+            runCurrent()
+            assertThat(padding).isEqualTo((defaultPadding * .5).toInt())
+        }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsLockscreenViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsLockscreenViewModelTest.kt
new file mode 100644
index 0000000..d58ceee
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsLockscreenViewModelTest.kt
@@ -0,0 +1,505 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.ui.viewmodel
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.settingslib.Utils
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
+import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.TransitionState
+import com.android.systemui.keyguard.shared.model.TransitionStep
+import com.google.common.collect.Range
+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.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.MockitoAnnotations
+
+/** Tests UDFPS lockscreen view model transitions. */
+@ExperimentalCoroutinesApi
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class UdfpsLockscreenViewModelTest : SysuiTestCase() {
+    private val lockscreenColorResId = android.R.attr.textColorPrimary
+    private val alternateBouncerResId = com.android.internal.R.attr.materialColorOnPrimaryFixed
+    private val lockscreenColor = Utils.getColorAttrDefaultColor(context, lockscreenColorResId)
+    private val alternateBouncerColor =
+        Utils.getColorAttrDefaultColor(context, alternateBouncerResId)
+
+    private lateinit var underTest: UdfpsLockscreenViewModel
+    private lateinit var testScope: TestScope
+    private lateinit var transitionRepository: FakeKeyguardTransitionRepository
+
+    @Before
+    fun setUp() {
+        MockitoAnnotations.initMocks(this)
+        testScope = TestScope()
+        transitionRepository = FakeKeyguardTransitionRepository()
+        val transitionInteractor =
+            KeyguardTransitionInteractor(
+                transitionRepository,
+                testScope.backgroundScope,
+            )
+        underTest =
+            UdfpsLockscreenViewModel(
+                context,
+                lockscreenColorResId,
+                alternateBouncerResId,
+                transitionInteractor,
+            )
+    }
+
+    @Test
+    fun goneToAodTransition() =
+        testScope.runTest {
+            val transition by collectLastValue(underTest.transition)
+            val visible by collectLastValue(underTest.visible)
+
+            // TransitionState.STARTED: gone -> AOD
+            transitionRepository.sendTransitionStep(
+                TransitionStep(
+                    from = KeyguardState.GONE,
+                    to = KeyguardState.AOD,
+                    value = 0f,
+                    transitionState = TransitionState.STARTED,
+                    ownerName = "goneToAodTransition",
+                )
+            )
+            runCurrent()
+            assertThat(transition?.alpha).isEqualTo(0f)
+            assertThat(visible).isFalse()
+
+            // TransitionState.RUNNING: gone -> AOD
+            transitionRepository.sendTransitionStep(
+                TransitionStep(
+                    from = KeyguardState.GONE,
+                    to = KeyguardState.AOD,
+                    value = .6f,
+                    transitionState = TransitionState.RUNNING,
+                    ownerName = "goneToAodTransition",
+                )
+            )
+            runCurrent()
+            assertThat(transition?.alpha).isEqualTo(0f)
+            assertThat(visible).isFalse()
+
+            // TransitionState.FINISHED: gone -> AOD
+            transitionRepository.sendTransitionStep(
+                TransitionStep(
+                    from = KeyguardState.GONE,
+                    to = KeyguardState.AOD,
+                    value = 1f,
+                    transitionState = TransitionState.FINISHED,
+                    ownerName = "goneToAodTransition",
+                )
+            )
+            runCurrent()
+            assertThat(transition?.alpha).isEqualTo(0f)
+            assertThat(visible).isFalse()
+        }
+
+    @Test
+    fun lockscreenToAod() =
+        testScope.runTest {
+            val transition by collectLastValue(underTest.transition)
+            val visible by collectLastValue(underTest.visible)
+
+            // TransitionState.STARTED: lockscreen -> AOD
+            transitionRepository.sendTransitionStep(
+                TransitionStep(
+                    from = KeyguardState.LOCKSCREEN,
+                    to = KeyguardState.AOD,
+                    value = 0f,
+                    transitionState = TransitionState.STARTED,
+                    ownerName = "lockscreenToAod",
+                )
+            )
+            runCurrent()
+            assertThat(transition?.alpha).isEqualTo(1f)
+            assertThat(transition?.scale).isEqualTo(1f)
+            assertThat(transition?.color).isEqualTo(lockscreenColor)
+            assertThat(visible).isTrue()
+
+            // TransitionState.RUNNING: lockscreen -> AOD
+            transitionRepository.sendTransitionStep(
+                TransitionStep(
+                    from = KeyguardState.LOCKSCREEN,
+                    to = KeyguardState.AOD,
+                    value = .6f,
+                    transitionState = TransitionState.RUNNING,
+                    ownerName = "lockscreenToAod",
+                )
+            )
+            runCurrent()
+            assertThat(transition?.alpha).isIn(Range.closed(.39f, .41f))
+            assertThat(transition?.scale).isEqualTo(1f)
+            assertThat(transition?.color).isEqualTo(lockscreenColor)
+            assertThat(visible).isTrue()
+
+            // TransitionState.FINISHED: lockscreen -> AOD
+            transitionRepository.sendTransitionStep(
+                TransitionStep(
+                    from = KeyguardState.LOCKSCREEN,
+                    to = KeyguardState.AOD,
+                    value = 1f,
+                    transitionState = TransitionState.FINISHED,
+                    ownerName = "lockscreenToAod",
+                )
+            )
+            runCurrent()
+            assertThat(transition?.alpha).isEqualTo(0f)
+            assertThat(transition?.scale).isEqualTo(1f)
+            assertThat(transition?.color).isEqualTo(lockscreenColor)
+            assertThat(visible).isFalse()
+        }
+
+    @Test
+    fun aodToLockscreen() =
+        testScope.runTest {
+            val transition by collectLastValue(underTest.transition)
+            val visible by collectLastValue(underTest.visible)
+
+            // TransitionState.STARTED: AOD -> lockscreen
+            transitionRepository.sendTransitionStep(
+                TransitionStep(
+                    from = KeyguardState.AOD,
+                    to = KeyguardState.LOCKSCREEN,
+                    value = 0f,
+                    transitionState = TransitionState.STARTED,
+                    ownerName = "aodToLockscreen",
+                )
+            )
+            runCurrent()
+            assertThat(transition?.alpha).isEqualTo(0f)
+            assertThat(transition?.scale).isEqualTo(1f)
+            assertThat(transition?.color).isEqualTo(lockscreenColor)
+            assertThat(visible).isFalse()
+
+            // TransitionState.RUNNING: AOD -> lockscreen
+            transitionRepository.sendTransitionStep(
+                TransitionStep(
+                    from = KeyguardState.AOD,
+                    to = KeyguardState.LOCKSCREEN,
+                    value = .6f,
+                    transitionState = TransitionState.RUNNING,
+                    ownerName = "aodToLockscreen",
+                )
+            )
+            runCurrent()
+            assertThat(transition?.alpha).isIn(Range.closed(.59f, .61f))
+            assertThat(transition?.scale).isEqualTo(1f)
+            assertThat(transition?.color).isEqualTo(lockscreenColor)
+            assertThat(visible).isTrue()
+
+            // TransitionState.FINISHED: AOD -> lockscreen
+            transitionRepository.sendTransitionStep(
+                TransitionStep(
+                    from = KeyguardState.AOD,
+                    to = KeyguardState.LOCKSCREEN,
+                    value = 1f,
+                    transitionState = TransitionState.FINISHED,
+                    ownerName = "aodToLockscreen",
+                )
+            )
+            runCurrent()
+            assertThat(transition?.alpha).isEqualTo(1f)
+            assertThat(transition?.scale).isEqualTo(1f)
+            assertThat(transition?.color).isEqualTo(lockscreenColor)
+            assertThat(visible).isTrue()
+        }
+
+    @Test
+    fun lockscreenToAlternateBouncer() =
+        testScope.runTest {
+            val transition by collectLastValue(underTest.transition)
+            val visible by collectLastValue(underTest.visible)
+
+            // TransitionState.STARTED: lockscreen -> alternate bouncer
+            transitionRepository.sendTransitionStep(
+                TransitionStep(
+                    from = KeyguardState.LOCKSCREEN,
+                    to = KeyguardState.ALTERNATE_BOUNCER,
+                    value = 0f,
+                    transitionState = TransitionState.STARTED,
+                    ownerName = "lockscreenToAlternateBouncer",
+                )
+            )
+            runCurrent()
+            assertThat(transition?.alpha).isEqualTo(1f)
+            assertThat(transition?.scale).isEqualTo(1f)
+            assertThat(transition?.color).isEqualTo(alternateBouncerColor)
+            assertThat(visible).isTrue()
+
+            // TransitionState.RUNNING: lockscreen -> alternate bouncer
+            transitionRepository.sendTransitionStep(
+                TransitionStep(
+                    from = KeyguardState.LOCKSCREEN,
+                    to = KeyguardState.ALTERNATE_BOUNCER,
+                    value = .6f,
+                    transitionState = TransitionState.RUNNING,
+                    ownerName = "lockscreenToAlternateBouncer",
+                )
+            )
+            runCurrent()
+            assertThat(transition?.alpha).isEqualTo(1f)
+            assertThat(transition?.scale).isEqualTo(1f)
+            assertThat(transition?.color).isEqualTo(alternateBouncerColor)
+            assertThat(visible).isTrue()
+
+            // TransitionState.FINISHED: lockscreen -> alternate bouncer
+            transitionRepository.sendTransitionStep(
+                TransitionStep(
+                    from = KeyguardState.LOCKSCREEN,
+                    to = KeyguardState.ALTERNATE_BOUNCER,
+                    value = 1f,
+                    transitionState = TransitionState.FINISHED,
+                    ownerName = "lockscreenToAlternateBouncer",
+                )
+            )
+            runCurrent()
+            assertThat(transition?.alpha).isEqualTo(1f)
+            assertThat(transition?.scale).isEqualTo(1f)
+            assertThat(transition?.color).isEqualTo(alternateBouncerColor)
+            assertThat(visible).isTrue()
+        }
+
+    fun alternateBouncerToPrimaryBouncer() =
+        testScope.runTest {
+            val transition by collectLastValue(underTest.transition)
+            val visible by collectLastValue(underTest.visible)
+
+            // TransitionState.STARTED: alternate bouncer -> primary bouncer
+            transitionRepository.sendTransitionStep(
+                TransitionStep(
+                    from = KeyguardState.ALTERNATE_BOUNCER,
+                    to = KeyguardState.PRIMARY_BOUNCER,
+                    value = 0f,
+                    transitionState = TransitionState.STARTED,
+                    ownerName = "alternateBouncerToPrimaryBouncer",
+                )
+            )
+            runCurrent()
+            assertThat(transition?.alpha).isEqualTo(1f)
+            assertThat(transition?.scale).isEqualTo(1f)
+            assertThat(transition?.color).isEqualTo(alternateBouncerColor)
+            assertThat(visible).isTrue()
+
+            // TransitionState.RUNNING: alternate bouncer -> primary bouncer
+            transitionRepository.sendTransitionStep(
+                TransitionStep(
+                    from = KeyguardState.ALTERNATE_BOUNCER,
+                    to = KeyguardState.PRIMARY_BOUNCER,
+                    value = .6f,
+                    transitionState = TransitionState.RUNNING,
+                    ownerName = "alternateBouncerToPrimaryBouncer",
+                )
+            )
+            runCurrent()
+            assertThat(transition?.alpha).isIn(Range.closed(.59f, .61f))
+            assertThat(transition?.scale).isEqualTo(1f)
+            assertThat(transition?.color).isEqualTo(alternateBouncerColor)
+            assertThat(visible).isTrue()
+
+            // TransitionState.FINISHED: alternate bouncer -> primary bouncer
+            transitionRepository.sendTransitionStep(
+                TransitionStep(
+                    from = KeyguardState.ALTERNATE_BOUNCER,
+                    to = KeyguardState.PRIMARY_BOUNCER,
+                    value = 1f,
+                    transitionState = TransitionState.FINISHED,
+                    ownerName = "alternateBouncerToPrimaryBouncer",
+                )
+            )
+            runCurrent()
+            assertThat(transition?.alpha).isEqualTo(0f)
+            assertThat(transition?.scale).isEqualTo(1f)
+            assertThat(transition?.color).isEqualTo(alternateBouncerColor)
+            assertThat(visible).isFalse()
+        }
+
+    fun alternateBouncerToAod() =
+        testScope.runTest {
+            val transition by collectLastValue(underTest.transition)
+            val visible by collectLastValue(underTest.visible)
+
+            // TransitionState.STARTED: alternate bouncer -> aod
+            transitionRepository.sendTransitionStep(
+                TransitionStep(
+                    from = KeyguardState.ALTERNATE_BOUNCER,
+                    to = KeyguardState.AOD,
+                    value = 0f,
+                    transitionState = TransitionState.STARTED,
+                    ownerName = "alternateBouncerToAod",
+                )
+            )
+            runCurrent()
+            assertThat(transition?.alpha).isEqualTo(1f)
+            assertThat(transition?.scale).isEqualTo(1f)
+            assertThat(transition?.color).isEqualTo(alternateBouncerColor)
+            assertThat(visible).isTrue()
+
+            // TransitionState.RUNNING: alternate bouncer -> aod
+            transitionRepository.sendTransitionStep(
+                TransitionStep(
+                    from = KeyguardState.ALTERNATE_BOUNCER,
+                    to = KeyguardState.AOD,
+                    value = .6f,
+                    transitionState = TransitionState.RUNNING,
+                    ownerName = "alternateBouncerToAod",
+                )
+            )
+            runCurrent()
+            assertThat(transition?.alpha).isIn(Range.closed(.39f, .41f))
+            assertThat(transition?.scale).isEqualTo(1f)
+            assertThat(transition?.color).isEqualTo(alternateBouncerColor)
+            assertThat(visible).isTrue()
+
+            // TransitionState.FINISHED: alternate bouncer -> aod
+            transitionRepository.sendTransitionStep(
+                TransitionStep(
+                    from = KeyguardState.ALTERNATE_BOUNCER,
+                    to = KeyguardState.AOD,
+                    value = 1f,
+                    transitionState = TransitionState.FINISHED,
+                    ownerName = "alternateBouncerToAod",
+                )
+            )
+            runCurrent()
+            assertThat(transition?.alpha).isEqualTo(0f)
+            assertThat(transition?.scale).isEqualTo(1f)
+            assertThat(transition?.color).isEqualTo(alternateBouncerColor)
+            assertThat(visible).isFalse()
+        }
+
+    @Test
+    fun lockscreenToOccluded() =
+        testScope.runTest {
+            val transition by collectLastValue(underTest.transition)
+            val visible by collectLastValue(underTest.visible)
+
+            // TransitionState.STARTED: lockscreen -> occluded
+            transitionRepository.sendTransitionStep(
+                TransitionStep(
+                    from = KeyguardState.LOCKSCREEN,
+                    to = KeyguardState.OCCLUDED,
+                    value = 0f,
+                    transitionState = TransitionState.STARTED,
+                    ownerName = "lockscreenToOccluded",
+                )
+            )
+            runCurrent()
+            assertThat(transition?.alpha).isEqualTo(1f)
+            assertThat(transition?.scale).isEqualTo(1f)
+            assertThat(transition?.color).isEqualTo(lockscreenColor)
+            assertThat(visible).isTrue()
+
+            // TransitionState.RUNNING: lockscreen -> occluded
+            transitionRepository.sendTransitionStep(
+                TransitionStep(
+                    from = KeyguardState.LOCKSCREEN,
+                    to = KeyguardState.OCCLUDED,
+                    value = .6f,
+                    transitionState = TransitionState.RUNNING,
+                    ownerName = "lockscreenToOccluded",
+                )
+            )
+            runCurrent()
+            assertThat(transition?.alpha).isIn(Range.closed(.39f, .41f))
+            assertThat(transition?.scale).isEqualTo(1f)
+            assertThat(transition?.color).isEqualTo(lockscreenColor)
+            assertThat(visible).isTrue()
+
+            // TransitionState.FINISHED: lockscreen -> occluded
+            transitionRepository.sendTransitionStep(
+                TransitionStep(
+                    from = KeyguardState.LOCKSCREEN,
+                    to = KeyguardState.OCCLUDED,
+                    value = 1f,
+                    transitionState = TransitionState.FINISHED,
+                    ownerName = "lockscreenToOccluded",
+                )
+            )
+            runCurrent()
+            assertThat(transition?.alpha).isEqualTo(0f)
+            assertThat(transition?.scale).isEqualTo(1f)
+            assertThat(transition?.color).isEqualTo(lockscreenColor)
+            assertThat(visible).isFalse()
+        }
+
+    @Test
+    fun occludedToLockscreen() =
+        testScope.runTest {
+            val transition by collectLastValue(underTest.transition)
+            val visible by collectLastValue(underTest.visible)
+
+            // TransitionState.STARTED: occluded -> lockscreen
+            transitionRepository.sendTransitionStep(
+                TransitionStep(
+                    from = KeyguardState.OCCLUDED,
+                    to = KeyguardState.LOCKSCREEN,
+                    value = 0f,
+                    transitionState = TransitionState.STARTED,
+                    ownerName = "occludedToLockscreen",
+                )
+            )
+            runCurrent()
+            assertThat(transition?.alpha).isEqualTo(1f)
+            assertThat(transition?.scale).isEqualTo(1f)
+            assertThat(transition?.color).isEqualTo(lockscreenColor)
+            assertThat(visible).isTrue()
+
+            // TransitionState.RUNNING: occluded -> lockscreen
+            transitionRepository.sendTransitionStep(
+                TransitionStep(
+                    from = KeyguardState.OCCLUDED,
+                    to = KeyguardState.LOCKSCREEN,
+                    value = .6f,
+                    transitionState = TransitionState.RUNNING,
+                    ownerName = "occludedToLockscreen",
+                )
+            )
+            runCurrent()
+            assertThat(transition?.alpha).isEqualTo(1f)
+            assertThat(transition?.scale).isEqualTo(1f)
+            assertThat(transition?.color).isEqualTo(lockscreenColor)
+            assertThat(visible).isTrue()
+
+            // TransitionState.FINISHED: occluded -> lockscreen
+            transitionRepository.sendTransitionStep(
+                TransitionStep(
+                    from = KeyguardState.OCCLUDED,
+                    to = KeyguardState.LOCKSCREEN,
+                    value = 1f,
+                    transitionState = TransitionState.FINISHED,
+                    ownerName = "occludedToLockscreen",
+                )
+            )
+            runCurrent()
+            assertThat(transition?.alpha).isEqualTo(1f)
+            assertThat(transition?.scale).isEqualTo(1f)
+            assertThat(transition?.color).isEqualTo(lockscreenColor)
+            assertThat(visible).isTrue()
+        }
+}
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 6836733..b5eae5b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/log/LogBufferTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/log/LogBufferTest.kt
@@ -2,7 +2,7 @@
 
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
-import com.android.systemui.log.core.LogLevel
+import com.android.systemui.log.core.Logger
 import com.google.common.truth.Truth.assertThat
 import java.io.PrintWriter
 import java.io.StringWriter
@@ -33,7 +33,8 @@
 
     @Test
     fun log_shouldSaveLogToBuffer() {
-        buffer.log("Test", LogLevel.INFO, "Some test message")
+        val logger = Logger(buffer, "Test")
+        logger.i("Some test message")
 
         val dumpedString = dumpBuffer()
 
@@ -42,8 +43,9 @@
 
     @Test
     fun log_shouldRotateIfLogBufferIsFull() {
-        buffer.log("Test", LogLevel.INFO, "This should be rotated")
-        buffer.log("Test", LogLevel.INFO, "New test message")
+        val logger = Logger(buffer, "Test")
+        logger.i("This should be rotated")
+        logger.i("New test message")
 
         val dumpedString = dumpBuffer()
 
@@ -54,7 +56,8 @@
     fun dump_writesExceptionAndStacktrace() {
         buffer = createBuffer()
         val exception = createTestException("Exception message", "TestClass")
-        buffer.log("Tag", LogLevel.ERROR, { str1 = "Extra message" }, { str1!! }, exception)
+        val logger = Logger(buffer, "Test")
+        logger.e("Extra message", exception)
 
         val dumpedString = dumpBuffer()
 
@@ -73,7 +76,8 @@
                 "TestClass",
                 cause = createTestException("The real cause!", "TestClass")
             )
-        buffer.log("Tag", LogLevel.ERROR, { str1 = "Extra message" }, { str1!! }, exception)
+        val logger = Logger(buffer, "Test")
+        logger.e("Extra message", exception)
 
         val dumpedString = dumpBuffer()
 
@@ -94,7 +98,8 @@
             )
         )
         exception.addSuppressed(createTestException("Second suppressed exception", "SecondClass"))
-        buffer.log("Tag", LogLevel.ERROR, { str1 = "Extra message" }, { str1!! }, exception)
+        val logger = Logger(buffer, "Test")
+        logger.e("Extra message", exception)
 
         val dumpedStr = dumpBuffer()
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/log/core/LoggerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/log/core/LoggerTest.kt
new file mode 100644
index 0000000..ab19b3a
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/log/core/LoggerTest.kt
@@ -0,0 +1,140 @@
+package com.android.systemui.log.core
+
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.log.LogMessageImpl
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.eq
+import com.android.systemui.util.mockito.nullable
+import com.android.systemui.util.mockito.whenever
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito.anyString
+import org.mockito.Mockito.isNull
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+import org.mockito.junit.MockitoJUnitRunner
+
+@SmallTest
+@RunWith(MockitoJUnitRunner::class)
+class LoggerTest : SysuiTestCase() {
+    @Mock private lateinit var buffer: MessageBuffer
+    private lateinit var message: LogMessage
+
+    @Before
+    fun setup() {
+        MockitoAnnotations.initMocks(this)
+        whenever(buffer.obtain(any(), any(), any(), isNull())).thenAnswer {
+            message = LogMessageImpl.Factory.create()
+            return@thenAnswer message
+        }
+    }
+
+    @Test
+    fun log_shouldCommitLogMessage() {
+        val logger = Logger(buffer, "LoggerTest")
+        logger.log(LogLevel.DEBUG, { "count=$int1" }) {
+            int1 = 1
+            str1 = "test"
+            bool1 = true
+        }
+
+        assertThat(message.int1).isEqualTo(1)
+        assertThat(message.str1).isEqualTo("test")
+        assertThat(message.bool1).isEqualTo(true)
+    }
+
+    @Test
+    fun log_shouldUseCorrectLoggerTag() {
+        val logger = Logger(buffer, "LoggerTest")
+        logger.log(LogLevel.DEBUG, { "count=$int1" }) { int1 = 1 }
+        verify(buffer).obtain(eq("LoggerTest"), any(), any(), nullable())
+    }
+
+    @Test
+    fun v_withMessageInitializer_shouldLogAtCorrectLevel() {
+        val logger = Logger(buffer, "LoggerTest")
+        logger.v({ "count=$int1" }) { int1 = 1 }
+        verify(buffer).obtain(anyString(), eq(LogLevel.VERBOSE), any(), nullable())
+    }
+
+    @Test
+    fun v_withCompileTimeMessage_shouldLogAtCorrectLevel() {
+        val logger = Logger(buffer, "LoggerTest")
+        logger.v("Message")
+        verify(buffer).obtain(anyString(), eq(LogLevel.VERBOSE), any(), nullable())
+    }
+
+    @Test
+    fun d_withMessageInitializer_shouldLogAtCorrectLevel() {
+        val logger = Logger(buffer, "LoggerTest")
+        logger.d({ "count=$int1" }) { int1 = 1 }
+        verify(buffer).obtain(anyString(), eq(LogLevel.DEBUG), any(), nullable())
+    }
+
+    @Test
+    fun d_withCompileTimeMessage_shouldLogAtCorrectLevel() {
+        val logger = Logger(buffer, "LoggerTest")
+        logger.d("Message")
+        verify(buffer).obtain(anyString(), eq(LogLevel.DEBUG), any(), nullable())
+    }
+
+    @Test
+    fun i_withMessageInitializer_shouldLogAtCorrectLevel() {
+        val logger = Logger(buffer, "LoggerTest")
+        logger.i({ "count=$int1" }) { int1 = 1 }
+        verify(buffer).obtain(anyString(), eq(LogLevel.INFO), any(), nullable())
+    }
+
+    @Test
+    fun i_withCompileTimeMessage_shouldLogAtCorrectLevel() {
+        val logger = Logger(buffer, "LoggerTest")
+        logger.i("Message")
+        verify(buffer).obtain(anyString(), eq(LogLevel.INFO), any(), nullable())
+    }
+
+    @Test
+    fun w_withMessageInitializer_shouldLogAtCorrectLevel() {
+        val logger = Logger(buffer, "LoggerTest")
+        logger.w({ "count=$int1" }) { int1 = 1 }
+        verify(buffer).obtain(anyString(), eq(LogLevel.WARNING), any(), nullable())
+    }
+
+    @Test
+    fun w_withCompileTimeMessage_shouldLogAtCorrectLevel() {
+        val logger = Logger(buffer, "LoggerTest")
+        logger.w("Message")
+        verify(buffer).obtain(anyString(), eq(LogLevel.WARNING), any(), nullable())
+    }
+
+    @Test
+    fun e_withMessageInitializer_shouldLogAtCorrectLevel() {
+        val logger = Logger(buffer, "LoggerTest")
+        logger.e({ "count=$int1" }) { int1 = 1 }
+        verify(buffer).obtain(anyString(), eq(LogLevel.ERROR), any(), nullable())
+    }
+
+    @Test
+    fun e_withCompileTimeMessage_shouldLogAtCorrectLevel() {
+        val logger = Logger(buffer, "LoggerTest")
+        logger.e("Message")
+        verify(buffer).obtain(anyString(), eq(LogLevel.ERROR), any(), nullable())
+    }
+
+    @Test
+    fun wtf_withMessageInitializer_shouldLogAtCorrectLevel() {
+        val logger = Logger(buffer, "LoggerTest")
+        logger.wtf({ "count=$int1" }) { int1 = 1 }
+        verify(buffer).obtain(anyString(), eq(LogLevel.WTF), any(), nullable())
+    }
+
+    @Test
+    fun wtf_withCompileTimeMessage_shouldLogAtCorrectLevel() {
+        val logger = Logger(buffer, "LoggerTest")
+        logger.wtf("Message")
+        verify(buffer).obtain(anyString(), eq(LogLevel.WTF), any(), nullable())
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/KeyguardMediaControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/KeyguardMediaControllerTest.kt
index b40ebc9..91b0245 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/KeyguardMediaControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/KeyguardMediaControllerTest.kt
@@ -193,6 +193,17 @@
     }
 
     @Test
+    fun dozeWakeUpAnimationWaiting_inSplitShade_mediaIsHidden() {
+        val splitShadeContainer = FrameLayout(context)
+        keyguardMediaController.attachSplitShadeContainer(splitShadeContainer)
+        keyguardMediaController.useSplitShade = true
+
+        keyguardMediaController.isDozeWakeUpAnimationWaiting = true
+
+        assertThat(splitShadeContainer.visibility).isEqualTo(GONE)
+    }
+
+    @Test
     fun dozing_inSingleShade_mediaIsVisible() {
         val splitShadeContainer = FrameLayout(context)
         keyguardMediaController.attachSplitShadeContainer(splitShadeContainer)
@@ -203,6 +214,17 @@
         assertThat(mediaContainerView.visibility).isEqualTo(VISIBLE)
     }
 
+    @Test
+    fun dozeWakeUpAnimationWaiting_inSingleShade_mediaIsVisible() {
+        val splitShadeContainer = FrameLayout(context)
+        keyguardMediaController.attachSplitShadeContainer(splitShadeContainer)
+        keyguardMediaController.useSplitShade = false
+
+        keyguardMediaController.isDozeWakeUpAnimationWaiting = true
+
+        assertThat(mediaContainerView.visibility).isEqualTo(VISIBLE)
+    }
+
     private fun setDozing() {
         whenever(statusBarStateController.isDozing).thenReturn(true)
         statusBarStateListener.onDozingChanged(true)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaControlPanelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaControlPanelTest.kt
index f902be3..dc771d7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaControlPanelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaControlPanelTest.kt
@@ -1793,7 +1793,7 @@
         // THEN it sends the PendingIntent without dismissing keyguard first,
         // and does not use the Intent directly (see b/271845008)
         captor.value.onClick(viewHolder.player)
-        verify(pendingIntent).send()
+        verify(pendingIntent).send(any(Bundle::class.java))
         verify(pendingIntent, never()).getIntent()
         verify(activityStarter, never()).postStartActivityDismissingKeyguard(eq(clickIntent), any())
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputAdapterTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputAdapterTest.java
index 7df54d4..e4f89a2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputAdapterTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputAdapterTest.java
@@ -291,13 +291,13 @@
 
         assertThat(mViewHolder.mTitleText.getVisibility()).isEqualTo(View.VISIBLE);
         assertThat(mViewHolder.mTitleText.getText().toString()).isEqualTo(TEST_DEVICE_NAME_1);
-        assertThat(mViewHolder.mStatusIcon.getVisibility()).isEqualTo(View.VISIBLE);
+        assertThat(mViewHolder.mStatusIcon.getVisibility()).isEqualTo(View.GONE);
         assertThat(mViewHolder.mSubTitleText.getVisibility()).isEqualTo(View.GONE);
         assertThat(mViewHolder.mProgressBar.getVisibility()).isEqualTo(View.GONE);
         assertThat(mViewHolder.mCheckBox.getVisibility()).isEqualTo(View.GONE);
         assertThat(mViewHolder.mTwoLineLayout.getVisibility()).isEqualTo(View.GONE);
-        assertThat(mViewHolder.mSeekBar.getVisibility()).isEqualTo(View.GONE);
-        assertThat(mViewHolder.mEndTouchArea.getVisibility()).isEqualTo(View.GONE);
+        assertThat(mViewHolder.mSeekBar.getVisibility()).isEqualTo(View.VISIBLE);
+        assertThat(mViewHolder.mEndTouchArea.getVisibility()).isEqualTo(View.VISIBLE);
     }
 
     @Test
@@ -525,16 +525,16 @@
         mMediaOutputAdapter.onBindViewHolder(mViewHolder, 0);
 
         assertThat(mViewHolder.mTitleText.getVisibility()).isEqualTo(View.GONE);
-        assertThat(mViewHolder.mSeekBar.getVisibility()).isEqualTo(View.GONE);
+        assertThat(mViewHolder.mSeekBar.getVisibility()).isEqualTo(View.VISIBLE);
         assertThat(mViewHolder.mProgressBar.getVisibility()).isEqualTo(View.GONE);
         assertThat(mViewHolder.mCheckBox.getVisibility()).isEqualTo(View.GONE);
-        assertThat(mViewHolder.mStatusIcon.getVisibility()).isEqualTo(View.VISIBLE);
+        assertThat(mViewHolder.mStatusIcon.getVisibility()).isEqualTo(View.GONE);
         assertThat(mViewHolder.mSubTitleText.getVisibility()).isEqualTo(View.VISIBLE);
         assertThat(mViewHolder.mTwoLineTitleText.getVisibility()).isEqualTo(View.VISIBLE);
         assertThat(mViewHolder.mSubTitleText.getText().toString()).isEqualTo(TEST_CUSTOM_SUBTEXT);
         assertThat(mViewHolder.mTwoLineTitleText.getText().toString()).isEqualTo(
                 TEST_DEVICE_NAME_1);
-        assertThat(mViewHolder.mContainerLayout.hasOnClickListeners()).isTrue();
+        assertThat(mViewHolder.mContainerLayout.hasOnClickListeners()).isFalse();
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/MediaProjectionTaskSwitcherCoreStartableTest.kt b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/MediaProjectionTaskSwitcherCoreStartableTest.kt
new file mode 100644
index 0000000..bcbf666
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/MediaProjectionTaskSwitcherCoreStartableTest.kt
@@ -0,0 +1,67 @@
+/*
+ * 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.mediaprojection.taskswitcher
+
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags
+import com.android.systemui.mediaprojection.taskswitcher.ui.TaskSwitcherNotificationCoordinator
+import com.android.systemui.util.mockito.whenever
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito.verify
+import org.mockito.Mockito.verifyZeroInteractions
+import org.mockito.MockitoAnnotations
+
+@RunWith(AndroidTestingRunner::class)
+@SmallTest
+class MediaProjectionTaskSwitcherCoreStartableTest : SysuiTestCase() {
+
+    @Mock private lateinit var flags: FeatureFlags
+    @Mock private lateinit var coordinator: TaskSwitcherNotificationCoordinator
+
+    private lateinit var coreStartable: MediaProjectionTaskSwitcherCoreStartable
+
+    @Before
+    fun setUp() {
+        MockitoAnnotations.initMocks(this)
+
+        coreStartable = MediaProjectionTaskSwitcherCoreStartable(coordinator, flags)
+    }
+
+    @Test
+    fun start_flagEnabled_startsCoordinator() {
+        whenever(flags.isEnabled(Flags.PARTIAL_SCREEN_SHARING_TASK_SWITCHER)).thenReturn(true)
+
+        coreStartable.start()
+
+        verify(coordinator).start()
+    }
+
+    @Test
+    fun start_flagDisabled_doesNotStartCoordinator() {
+        whenever(flags.isEnabled(Flags.PARTIAL_SCREEN_SHARING_TASK_SWITCHER)).thenReturn(false)
+
+        coreStartable.start()
+
+        verifyZeroInteractions(coordinator)
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/ActivityTaskManagerTasksRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/ActivityTaskManagerTasksRepositoryTest.kt
new file mode 100644
index 0000000..83932b0
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/ActivityTaskManagerTasksRepositoryTest.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.mediaprojection.taskswitcher.data.repository
+
+import android.os.Binder
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.mediaprojection.taskswitcher.data.repository.FakeActivityTaskManager.Companion.createTask
+import com.android.systemui.mediaprojection.taskswitcher.data.repository.FakeActivityTaskManager.Companion.createToken
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.UnconfinedTestDispatcher
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@RunWith(AndroidTestingRunner::class)
+@SmallTest
+class ActivityTaskManagerTasksRepositoryTest : SysuiTestCase() {
+
+    private val fakeActivityTaskManager = FakeActivityTaskManager()
+
+    private val dispatcher = UnconfinedTestDispatcher()
+    private val testScope = TestScope(dispatcher)
+
+    private val repo =
+        ActivityTaskManagerTasksRepository(
+            activityTaskManager = fakeActivityTaskManager.activityTaskManager,
+            applicationScope = testScope.backgroundScope,
+            backgroundDispatcher = dispatcher
+        )
+
+    @Test
+    fun findRunningTaskFromWindowContainerToken_noMatch_returnsNull() {
+        fakeActivityTaskManager.addRunningTasks(createTask(taskId = 1), createTask(taskId = 2))
+
+        testScope.runTest {
+            val matchingTask =
+                repo.findRunningTaskFromWindowContainerToken(windowContainerToken = Binder())
+
+            assertThat(matchingTask).isNull()
+        }
+    }
+
+    @Test
+    fun findRunningTaskFromWindowContainerToken_matchingToken_returnsTaskInfo() {
+        val expectedToken = createToken()
+        val expectedTask = createTask(taskId = 1, token = expectedToken)
+
+        fakeActivityTaskManager.addRunningTasks(
+            createTask(taskId = 2),
+            expectedTask,
+        )
+
+        testScope.runTest {
+            val actualTask =
+                repo.findRunningTaskFromWindowContainerToken(
+                    windowContainerToken = expectedToken.asBinder()
+                )
+
+            assertThat(actualTask).isEqualTo(expectedTask)
+        }
+    }
+
+    @Test
+    fun foregroundTask_returnsStreamOfTasksMovedToFront() =
+        testScope.runTest {
+            val foregroundTask by collectLastValue(repo.foregroundTask)
+
+            fakeActivityTaskManager.moveTaskToForeground(createTask(taskId = 1))
+            assertThat(foregroundTask?.taskId).isEqualTo(1)
+
+            fakeActivityTaskManager.moveTaskToForeground(createTask(taskId = 2))
+            assertThat(foregroundTask?.taskId).isEqualTo(2)
+
+            fakeActivityTaskManager.moveTaskToForeground(createTask(taskId = 3))
+            assertThat(foregroundTask?.taskId).isEqualTo(3)
+        }
+
+    @Test
+    fun foregroundTask_lastValueIsCached() =
+        testScope.runTest {
+            val foregroundTaskA by collectLastValue(repo.foregroundTask)
+            fakeActivityTaskManager.moveTaskToForeground(createTask(taskId = 1))
+            assertThat(foregroundTaskA?.taskId).isEqualTo(1)
+
+            val foregroundTaskB by collectLastValue(repo.foregroundTask)
+            assertThat(foregroundTaskB?.taskId).isEqualTo(1)
+        }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/FakeActivityTaskManager.kt b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/FakeActivityTaskManager.kt
new file mode 100644
index 0000000..1c4870b
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/FakeActivityTaskManager.kt
@@ -0,0 +1,77 @@
+/*
+ * 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.mediaprojection.taskswitcher.data.repository
+
+import android.app.ActivityManager.RunningTaskInfo
+import android.app.ActivityTaskManager
+import android.app.TaskStackListener
+import android.content.Intent
+import android.window.IWindowContainerToken
+import android.window.WindowContainerToken
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.whenever
+
+class FakeActivityTaskManager {
+
+    private val runningTasks = mutableListOf<RunningTaskInfo>()
+    private val taskTaskListeners = mutableListOf<TaskStackListener>()
+
+    val activityTaskManager = mock<ActivityTaskManager>()
+
+    init {
+        whenever(activityTaskManager.registerTaskStackListener(any())).thenAnswer {
+            taskTaskListeners += it.arguments[0] as TaskStackListener
+            return@thenAnswer Unit
+        }
+        whenever(activityTaskManager.unregisterTaskStackListener(any())).thenAnswer {
+            taskTaskListeners -= it.arguments[0] as TaskStackListener
+            return@thenAnswer Unit
+        }
+        whenever(activityTaskManager.getTasks(any())).thenAnswer {
+            val maxNumTasks = it.arguments[0] as Int
+            return@thenAnswer runningTasks.take(maxNumTasks)
+        }
+    }
+
+    fun moveTaskToForeground(task: RunningTaskInfo) {
+        taskTaskListeners.forEach { it.onTaskMovedToFront(task) }
+    }
+
+    fun addRunningTasks(vararg tasks: RunningTaskInfo) {
+        runningTasks += tasks
+    }
+
+    companion object {
+
+        fun createTask(
+            taskId: Int,
+            token: WindowContainerToken = createToken(),
+            baseIntent: Intent = Intent()
+        ) =
+            RunningTaskInfo().apply {
+                this.taskId = taskId
+                this.token = token
+                this.baseIntent = baseIntent
+            }
+
+        fun createToken(): WindowContainerToken {
+            val realToken = object : IWindowContainerToken.Stub() {}
+            return WindowContainerToken(realToken)
+        }
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/FakeMediaProjectionRepository.kt b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/FakeMediaProjectionRepository.kt
new file mode 100644
index 0000000..c59fd60
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/FakeMediaProjectionRepository.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.mediaprojection.taskswitcher.data.repository
+
+import android.app.TaskInfo
+import com.android.systemui.mediaprojection.taskswitcher.data.model.MediaProjectionState
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.asStateFlow
+
+class FakeMediaProjectionRepository : MediaProjectionRepository {
+
+    private val state = MutableStateFlow<MediaProjectionState>(MediaProjectionState.NotProjecting)
+
+    fun switchProjectedTask(newTask: TaskInfo) {
+        state.value = MediaProjectionState.SingleTask(newTask)
+    }
+
+    override val mediaProjectionState: Flow<MediaProjectionState> = state.asStateFlow()
+
+    fun projectEntireScreen() {
+        state.value = MediaProjectionState.EntireScreen
+    }
+
+    fun stopProjecting() {
+        state.value = MediaProjectionState.NotProjecting
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/FakeTasksRepository.kt b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/FakeTasksRepository.kt
new file mode 100644
index 0000000..593e389
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/FakeTasksRepository.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.mediaprojection.taskswitcher.data.repository
+
+import android.app.ActivityManager.RunningTaskInfo
+import android.content.Intent
+import android.os.IBinder
+import android.window.IWindowContainerToken
+import android.window.WindowContainerToken
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.asStateFlow
+
+class FakeTasksRepository : TasksRepository {
+
+    private val _foregroundTask = MutableStateFlow(DEFAULT_TASK)
+
+    override val foregroundTask: Flow<RunningTaskInfo> = _foregroundTask.asStateFlow()
+
+    private val runningTasks = mutableListOf(DEFAULT_TASK)
+
+    override suspend fun findRunningTaskFromWindowContainerToken(
+        windowContainerToken: IBinder
+    ): RunningTaskInfo? = runningTasks.firstOrNull { it.token.asBinder() == windowContainerToken }
+
+    fun addRunningTask(task: RunningTaskInfo) {
+        runningTasks.add(task)
+    }
+
+    fun moveTaskToForeground(task: RunningTaskInfo) {
+        _foregroundTask.value = task
+    }
+
+    companion object {
+        val DEFAULT_TASK = createTask(taskId = -1)
+        val LAUNCHER_INTENT: Intent = Intent(Intent.ACTION_MAIN).addCategory(Intent.CATEGORY_HOME)
+
+        fun createTask(
+            taskId: Int,
+            token: WindowContainerToken = createToken(),
+            baseIntent: Intent = Intent()
+        ) =
+            RunningTaskInfo().apply {
+                this.taskId = taskId
+                this.token = token
+                this.baseIntent = baseIntent
+            }
+
+        fun createToken(): WindowContainerToken {
+            val realToken = object : IWindowContainerToken.Stub() {}
+            return WindowContainerToken(realToken)
+        }
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/MediaProjectionManagerRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/MediaProjectionManagerRepositoryTest.kt
new file mode 100644
index 0000000..2b07465
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/MediaProjectionManagerRepositoryTest.kt
@@ -0,0 +1,162 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.mediaprojection.taskswitcher.data.repository
+
+import android.media.projection.MediaProjectionInfo
+import android.media.projection.MediaProjectionManager
+import android.os.Binder
+import android.os.Handler
+import android.os.UserHandle
+import android.testing.AndroidTestingRunner
+import android.view.ContentRecordingSession
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.mediaprojection.taskswitcher.data.model.MediaProjectionState
+import com.android.systemui.util.mockito.any
+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
+import org.junit.runner.RunWith
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@RunWith(AndroidTestingRunner::class)
+@SmallTest
+class MediaProjectionManagerRepositoryTest : SysuiTestCase() {
+
+    private val mediaProjectionManager = mock<MediaProjectionManager>()
+
+    private val dispatcher = StandardTestDispatcher()
+    private val testScope = TestScope(dispatcher)
+    private val tasksRepo = FakeTasksRepository()
+
+    private lateinit var callback: MediaProjectionManager.Callback
+    private lateinit var repo: MediaProjectionManagerRepository
+
+    @Before
+    fun setUp() {
+        whenever(mediaProjectionManager.addCallback(any(), any())).thenAnswer {
+            callback = it.arguments[0] as MediaProjectionManager.Callback
+            return@thenAnswer Unit
+        }
+        repo =
+            MediaProjectionManagerRepository(
+                mediaProjectionManager = mediaProjectionManager,
+                handler = Handler.getMain(),
+                applicationScope = testScope.backgroundScope,
+                tasksRepository = tasksRepo
+            )
+    }
+
+    @Test
+    fun mediaProjectionState_onStart_emitsNotProjecting() =
+        testScope.runTest {
+            val state by collectLastValue(repo.mediaProjectionState)
+            runCurrent()
+
+            callback.onStart(TEST_MEDIA_INFO)
+
+            assertThat(state).isEqualTo(MediaProjectionState.NotProjecting)
+        }
+
+    @Test
+    fun mediaProjectionState_onStop_emitsNotProjecting() =
+        testScope.runTest {
+            val state by collectLastValue(repo.mediaProjectionState)
+            runCurrent()
+
+            callback.onStop(TEST_MEDIA_INFO)
+
+            assertThat(state).isEqualTo(MediaProjectionState.NotProjecting)
+        }
+
+    @Test
+    fun mediaProjectionState_onSessionSet_sessionNull_emitsNotProjecting() =
+        testScope.runTest {
+            val state by collectLastValue(repo.mediaProjectionState)
+            runCurrent()
+
+            callback.onRecordingSessionSet(TEST_MEDIA_INFO, /* session= */ null)
+
+            assertThat(state).isEqualTo(MediaProjectionState.NotProjecting)
+        }
+
+    @Test
+    fun mediaProjectionState_onSessionSet_contentToRecordDisplay_emitsEntireScreen() =
+        testScope.runTest {
+            val state by collectLastValue(repo.mediaProjectionState)
+            runCurrent()
+
+            val session = ContentRecordingSession.createDisplaySession(/* displayToMirror= */ 123)
+            callback.onRecordingSessionSet(TEST_MEDIA_INFO, session)
+
+            assertThat(state).isEqualTo(MediaProjectionState.EntireScreen)
+        }
+
+    @Test
+    fun mediaProjectionState_onSessionSet_tokenNull_emitsEntireScreen() =
+        testScope.runTest {
+            val state by collectLastValue(repo.mediaProjectionState)
+            runCurrent()
+
+            val session =
+                ContentRecordingSession.createTaskSession(/* taskWindowContainerToken= */ null)
+            callback.onRecordingSessionSet(TEST_MEDIA_INFO, session)
+
+            assertThat(state).isEqualTo(MediaProjectionState.EntireScreen)
+        }
+
+    @Test
+    fun mediaProjectionState_sessionSet_taskWithToken_noMatchingRunningTask_emitsEntireScreen() =
+        testScope.runTest {
+            val state by collectLastValue(repo.mediaProjectionState)
+            runCurrent()
+
+            val taskWindowContainerToken = Binder()
+            val session = ContentRecordingSession.createTaskSession(taskWindowContainerToken)
+            callback.onRecordingSessionSet(TEST_MEDIA_INFO, session)
+
+            assertThat(state).isEqualTo(MediaProjectionState.EntireScreen)
+        }
+
+    @Test
+    fun mediaProjectionState_sessionSet_taskWithToken_matchingRunningTask_emitsSingleTask() =
+        testScope.runTest {
+            val token = FakeTasksRepository.createToken()
+            val task = FakeTasksRepository.createTask(taskId = 1, token = token)
+            tasksRepo.addRunningTask(task)
+            val state by collectLastValue(repo.mediaProjectionState)
+            runCurrent()
+
+            val session = ContentRecordingSession.createTaskSession(token.asBinder())
+            callback.onRecordingSessionSet(TEST_MEDIA_INFO, session)
+
+            assertThat(state).isEqualTo(MediaProjectionState.SingleTask(task))
+        }
+
+    companion object {
+        val TEST_MEDIA_INFO =
+            MediaProjectionInfo(/* packageName= */ "com.test.package", UserHandle.CURRENT)
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/domain/interactor/TaskSwitchInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/domain/interactor/TaskSwitchInteractorTest.kt
new file mode 100644
index 0000000..112950b
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/domain/interactor/TaskSwitchInteractorTest.kt
@@ -0,0 +1,127 @@
+/*
+ * 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.mediaprojection.taskswitcher.domain.interactor
+
+import android.content.Intent
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.mediaprojection.taskswitcher.data.repository.ActivityTaskManagerTasksRepository
+import com.android.systemui.mediaprojection.taskswitcher.data.repository.FakeActivityTaskManager
+import com.android.systemui.mediaprojection.taskswitcher.data.repository.FakeActivityTaskManager.Companion.createTask
+import com.android.systemui.mediaprojection.taskswitcher.data.repository.FakeMediaProjectionRepository
+import com.android.systemui.mediaprojection.taskswitcher.domain.model.TaskSwitchState
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.UnconfinedTestDispatcher
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@RunWith(AndroidTestingRunner::class)
+@SmallTest
+class TaskSwitchInteractorTest : SysuiTestCase() {
+
+    private val dispatcher = UnconfinedTestDispatcher()
+    private val testScope = TestScope(dispatcher)
+
+    private val fakeActivityTaskManager = FakeActivityTaskManager()
+    private val mediaRepo = FakeMediaProjectionRepository()
+    private val tasksRepo =
+        ActivityTaskManagerTasksRepository(
+            activityTaskManager = fakeActivityTaskManager.activityTaskManager,
+            applicationScope = testScope.backgroundScope,
+            backgroundDispatcher = dispatcher
+        )
+
+    private val interactor = TaskSwitchInteractor(mediaRepo, tasksRepo)
+
+    @Test
+    fun taskSwitchChanges_notProjecting_foregroundTaskChange_emitsNotProjectingTask() =
+        testScope.runTest {
+            mediaRepo.stopProjecting()
+            val taskSwitchState by collectLastValue(interactor.taskSwitchChanges)
+
+            fakeActivityTaskManager.moveTaskToForeground(createTask(taskId = 1))
+
+            assertThat(taskSwitchState).isEqualTo(TaskSwitchState.NotProjectingTask)
+        }
+
+    @Test
+    fun taskSwitchChanges_projectingScreen_foregroundTaskChange_emitsNotProjectingTask() =
+        testScope.runTest {
+            mediaRepo.projectEntireScreen()
+            val taskSwitchState by collectLastValue(interactor.taskSwitchChanges)
+
+            fakeActivityTaskManager.moveTaskToForeground(createTask(taskId = 1))
+
+            assertThat(taskSwitchState).isEqualTo(TaskSwitchState.NotProjectingTask)
+        }
+
+    @Test
+    fun taskSwitchChanges_projectingTask_foregroundTaskDifferent_emitsTaskChanged() =
+        testScope.runTest {
+            val projectedTask = createTask(taskId = 0)
+            val foregroundTask = createTask(taskId = 1)
+            mediaRepo.switchProjectedTask(projectedTask)
+            val taskSwitchState by collectLastValue(interactor.taskSwitchChanges)
+
+            fakeActivityTaskManager.moveTaskToForeground(foregroundTask)
+
+            assertThat(taskSwitchState)
+                .isEqualTo(
+                    TaskSwitchState.TaskSwitched(
+                        projectedTask = projectedTask,
+                        foregroundTask = foregroundTask
+                    )
+                )
+        }
+
+    @Test
+    fun taskSwitchChanges_projectingTask_foregroundTaskLauncher_emitsTaskUnchanged() =
+        testScope.runTest {
+            val projectedTask = createTask(taskId = 0)
+            val foregroundTask = createTask(taskId = 1, baseIntent = LAUNCHER_INTENT)
+            mediaRepo.switchProjectedTask(projectedTask)
+            val taskSwitchState by collectLastValue(interactor.taskSwitchChanges)
+
+            fakeActivityTaskManager.moveTaskToForeground(foregroundTask)
+
+            assertThat(taskSwitchState).isEqualTo(TaskSwitchState.TaskUnchanged)
+        }
+
+    @Test
+    fun taskSwitchChanges_projectingTask_foregroundTaskSame_emitsTaskUnchanged() =
+        testScope.runTest {
+            val projectedTask = createTask(taskId = 0)
+            val foregroundTask = createTask(taskId = 0)
+            mediaRepo.switchProjectedTask(projectedTask)
+            val taskSwitchState by collectLastValue(interactor.taskSwitchChanges)
+
+            fakeActivityTaskManager.moveTaskToForeground(foregroundTask)
+
+            assertThat(taskSwitchState).isEqualTo(TaskSwitchState.TaskUnchanged)
+        }
+
+    companion object {
+        private val LAUNCHER_INTENT: Intent =
+            Intent(Intent.ACTION_MAIN).addCategory(Intent.CATEGORY_HOME)
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/ui/viewmodel/TaskSwitcherNotificationViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/ui/viewmodel/TaskSwitcherNotificationViewModelTest.kt
new file mode 100644
index 0000000..ea44fb3
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/ui/viewmodel/TaskSwitcherNotificationViewModelTest.kt
@@ -0,0 +1,141 @@
+/*
+ * 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.mediaprojection.taskswitcher.ui.viewmodel
+
+import android.content.Intent
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.mediaprojection.taskswitcher.data.repository.ActivityTaskManagerTasksRepository
+import com.android.systemui.mediaprojection.taskswitcher.data.repository.FakeActivityTaskManager
+import com.android.systemui.mediaprojection.taskswitcher.data.repository.FakeActivityTaskManager.Companion.createTask
+import com.android.systemui.mediaprojection.taskswitcher.data.repository.FakeMediaProjectionRepository
+import com.android.systemui.mediaprojection.taskswitcher.domain.interactor.TaskSwitchInteractor
+import com.android.systemui.mediaprojection.taskswitcher.ui.model.TaskSwitcherNotificationUiState
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.UnconfinedTestDispatcher
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@RunWith(AndroidTestingRunner::class)
+@SmallTest
+class TaskSwitcherNotificationViewModelTest : SysuiTestCase() {
+
+    private val dispatcher = UnconfinedTestDispatcher()
+    private val testScope = TestScope(dispatcher)
+
+    private val fakeActivityTaskManager = FakeActivityTaskManager()
+    private val mediaRepo = FakeMediaProjectionRepository()
+    private val tasksRepo =
+        ActivityTaskManagerTasksRepository(
+            activityTaskManager = fakeActivityTaskManager.activityTaskManager,
+            applicationScope = testScope.backgroundScope,
+            backgroundDispatcher = dispatcher
+        )
+    private val interactor = TaskSwitchInteractor(mediaRepo, tasksRepo)
+
+    private val viewModel = TaskSwitcherNotificationViewModel(interactor)
+
+    @Test
+    fun uiState_notProjecting_emitsNotShowing() =
+        testScope.runTest {
+            mediaRepo.stopProjecting()
+            val uiState by collectLastValue(viewModel.uiState)
+
+            assertThat(uiState).isEqualTo(TaskSwitcherNotificationUiState.NotShowing)
+        }
+
+    @Test
+    fun uiState_notProjecting_foregroundTaskChanged_emitsNotShowing() =
+        testScope.runTest {
+            mediaRepo.stopProjecting()
+            val uiState by collectLastValue(viewModel.uiState)
+
+            mediaRepo.switchProjectedTask(createTask(taskId = 1))
+
+            assertThat(uiState).isEqualTo(TaskSwitcherNotificationUiState.NotShowing)
+        }
+
+    @Test
+    fun uiState_projectingEntireScreen_emitsNotShowing() =
+        testScope.runTest {
+            mediaRepo.projectEntireScreen()
+            val uiState by collectLastValue(viewModel.uiState)
+
+            assertThat(uiState).isEqualTo(TaskSwitcherNotificationUiState.NotShowing)
+        }
+
+    @Test
+    fun uiState_projectingEntireScreen_foregroundTaskChanged_emitsNotShowing() =
+        testScope.runTest {
+            mediaRepo.projectEntireScreen()
+            val uiState by collectLastValue(viewModel.uiState)
+
+            mediaRepo.switchProjectedTask(createTask(taskId = 1))
+
+            assertThat(uiState).isEqualTo(TaskSwitcherNotificationUiState.NotShowing)
+        }
+
+    @Test
+    fun uiState_projectingTask_foregroundTaskChanged_different_emitsShowing() =
+        testScope.runTest {
+            val projectedTask = createTask(taskId = 1)
+            val foregroundTask = createTask(taskId = 2)
+            mediaRepo.switchProjectedTask(projectedTask)
+            val uiState by collectLastValue(viewModel.uiState)
+
+            fakeActivityTaskManager.moveTaskToForeground(foregroundTask)
+
+            assertThat(uiState)
+                .isEqualTo(TaskSwitcherNotificationUiState.Showing(projectedTask, foregroundTask))
+        }
+
+    @Test
+    fun uiState_projectingTask_foregroundTaskChanged_same_emitsNotShowing() =
+        testScope.runTest {
+            val projectedTask = createTask(taskId = 1)
+            mediaRepo.switchProjectedTask(projectedTask)
+            val uiState by collectLastValue(viewModel.uiState)
+
+            fakeActivityTaskManager.moveTaskToForeground(projectedTask)
+
+            assertThat(uiState).isEqualTo(TaskSwitcherNotificationUiState.NotShowing)
+        }
+
+    @Test
+    fun uiState_projectingTask_foregroundTaskChanged_different_taskIsLauncher_emitsNotShowing() =
+        testScope.runTest {
+            val projectedTask = createTask(taskId = 1)
+            val foregroundTask = createTask(taskId = 2, baseIntent = LAUNCHER_INTENT)
+            mediaRepo.switchProjectedTask(projectedTask)
+            val uiState by collectLastValue(viewModel.uiState)
+
+            fakeActivityTaskManager.moveTaskToForeground(foregroundTask)
+
+            assertThat(uiState).isEqualTo(TaskSwitcherNotificationUiState.NotShowing)
+        }
+
+    companion object {
+        private val LAUNCHER_INTENT: Intent =
+            Intent(Intent.ACTION_MAIN).addCategory(Intent.CATEGORY_HOME)
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/multishade/domain/interactor/MultiShadeMotionEventInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/multishade/domain/interactor/MultiShadeMotionEventInteractorTest.kt
index 9e54224..5890cbd 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/multishade/domain/interactor/MultiShadeMotionEventInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/multishade/domain/interactor/MultiShadeMotionEventInteractorTest.kt
@@ -25,7 +25,7 @@
 import com.android.systemui.flags.FakeFeatureFlags
 import com.android.systemui.flags.Flags
 import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
-import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractorFactory
 import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.keyguard.shared.model.TransitionState
 import com.android.systemui.keyguard.shared.model.TransitionStep
@@ -97,10 +97,11 @@
                 multiShadeInteractor = interactor,
                 featureFlags = featureFlags,
                 keyguardTransitionInteractor =
-                    KeyguardTransitionInteractor(
-                        repository = keyguardTransitionRepository,
-                        scope = testScope.backgroundScope
-                    ),
+                    KeyguardTransitionInteractorFactory.create(
+                            scope = TestScope().backgroundScope,
+                            repository = keyguardTransitionRepository,
+                        )
+                        .keyguardTransitionInteractor,
                 falsingManager = falsingManager,
                 shadeController = shadeController,
             )
diff --git a/packages/SystemUI/tests/src/com/android/systemui/notetask/LaunchNotesRoleSettingsTrampolineActivityTest.kt b/packages/SystemUI/tests/src/com/android/systemui/notetask/LaunchNotesRoleSettingsTrampolineActivityTest.kt
index 36b913f..bdb095a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/notetask/LaunchNotesRoleSettingsTrampolineActivityTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/notetask/LaunchNotesRoleSettingsTrampolineActivityTest.kt
@@ -22,9 +22,9 @@
 import android.testing.TestableLooper
 import androidx.test.filters.SmallTest
 import androidx.test.rule.ActivityTestRule
-import androidx.test.runner.intercepting.SingleActivityFactory
 import com.android.dx.mockito.inline.extended.ExtendedMockito.verify
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.activity.SingleActivityFactory
 import com.android.systemui.notetask.LaunchNotesRoleSettingsTrampolineActivity.Companion.ACTION_MANAGE_NOTES_ROLE_FROM_QUICK_AFFORDANCE
 import com.android.systemui.notetask.NoteTaskEntryPoint.QUICK_AFFORDANCE
 import com.android.systemui.util.mockito.any
@@ -47,13 +47,9 @@
     @Rule
     @JvmField
     val activityRule =
-        ActivityTestRule<LaunchNotesRoleSettingsTrampolineActivity>(
-            /* activityFactory= */ object :
-                SingleActivityFactory<LaunchNotesRoleSettingsTrampolineActivity>(
-                    LaunchNotesRoleSettingsTrampolineActivity::class.java
-                ) {
-                override fun create(intent: Intent?) =
-                    LaunchNotesRoleSettingsTrampolineActivity(noteTaskController)
+        ActivityTestRule(
+            /* activityFactory= */ SingleActivityFactory {
+                LaunchNotesRoleSettingsTrampolineActivity(noteTaskController)
             },
             /* initialTouchMode= */ false,
             /* launchActivity= */ false,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/notetask/shortcut/LaunchNoteTaskActivityTest.kt b/packages/SystemUI/tests/src/com/android/systemui/notetask/shortcut/LaunchNoteTaskActivityTest.kt
index 627c4a8..1f0f0d7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/notetask/shortcut/LaunchNoteTaskActivityTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/notetask/shortcut/LaunchNoteTaskActivityTest.kt
@@ -16,14 +16,13 @@
 
 package com.android.systemui.notetask.shortcut
 
-import android.content.Intent
 import android.testing.AndroidTestingRunner
 import android.testing.TestableLooper
 import androidx.test.filters.SmallTest
 import androidx.test.rule.ActivityTestRule
-import androidx.test.runner.intercepting.SingleActivityFactory
 import com.android.dx.mockito.inline.extended.ExtendedMockito.verify
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.activity.SingleActivityFactory
 import com.android.systemui.notetask.NoteTaskController
 import com.android.systemui.notetask.NoteTaskEntryPoint
 import com.android.systemui.util.mockito.any
@@ -47,12 +46,8 @@
     @JvmField
     val activityRule =
         ActivityTestRule<LaunchNoteTaskActivity>(
-            /* activityFactory= */ object :
-                SingleActivityFactory<LaunchNoteTaskActivity>(LaunchNoteTaskActivity::class.java) {
-                override fun create(intent: Intent?) =
-                    LaunchNoteTaskActivity(
-                        controller = noteTaskController,
-                    )
+            /* activityFactory= */ SingleActivityFactory {
+                LaunchNoteTaskActivity(controller = noteTaskController)
             },
             /* initialTouchMode= */ false,
             /* launchActivity= */ false,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/data/repository/InstalledTilesComponentRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/data/repository/InstalledTilesComponentRepositoryImplTest.kt
index 18f3837..dc0fae5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/data/repository/InstalledTilesComponentRepositoryImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/data/repository/InstalledTilesComponentRepositoryImplTest.kt
@@ -38,6 +38,7 @@
 import com.android.systemui.util.mockito.argumentCaptor
 import com.android.systemui.util.mockito.capture
 import com.android.systemui.util.mockito.eq
+import com.android.systemui.util.mockito.mock
 import com.android.systemui.util.mockito.nullable
 import com.android.systemui.util.mockito.whenever
 import com.google.common.truth.Truth.assertThat
@@ -75,6 +76,9 @@
     fun setUp() {
         MockitoAnnotations.initMocks(this)
 
+        whenever(context.createContextAsUser(any(), anyInt())).thenReturn(context)
+        whenever(context.packageManager).thenReturn(packageManager)
+
         // Use the default value set in the ServiceInfo
         whenever(packageManager.getComponentEnabledSetting(any()))
             .thenReturn(PackageManager.COMPONENT_ENABLED_STATE_DEFAULT)
@@ -86,7 +90,6 @@
         underTest =
             InstalledTilesComponentRepositoryImpl(
                 context,
-                packageManager,
                 testDispatcher,
             )
     }
@@ -224,6 +227,52 @@
             assertThat(componentNames).isEmpty()
         }
 
+    @Test
+    fun packageOnlyInSecondaryUser_noException() =
+        testScope.runTest {
+            val userId = 10
+            val secondaryUserContext: Context = mock()
+            whenever(context.userId).thenReturn(0) // System context
+            whenever(context.createContextAsUser(eq(UserHandle.of(userId)), anyInt()))
+                .thenReturn(secondaryUserContext)
+
+            val secondaryUserPackageManager: PackageManager = mock()
+            whenever(secondaryUserContext.packageManager).thenReturn(secondaryUserPackageManager)
+
+            // Use the default value set in the ServiceInfo
+            whenever(secondaryUserPackageManager.getComponentEnabledSetting(any()))
+                .thenReturn(PackageManager.COMPONENT_ENABLED_STATE_DEFAULT)
+            // System User package manager throws exception if the component doesn't exist for that
+            // user
+            whenever(packageManager.getComponentEnabledSetting(TEST_COMPONENT))
+                .thenThrow(IllegalArgumentException()) // The package is not in the system user
+
+            val resolveInfo =
+                ResolveInfo(TEST_COMPONENT, hasPermission = true, defaultEnabled = true)
+            // Both package manager should return the same (because the query is for the secondary
+            // user)
+            whenever(
+                    secondaryUserPackageManager.queryIntentServicesAsUser(
+                        matchIntent(),
+                        matchFlags(),
+                        eq(userId)
+                    )
+                )
+                .thenReturn(listOf(resolveInfo))
+            whenever(
+                    packageManager.queryIntentServicesAsUser(
+                        matchIntent(),
+                        matchFlags(),
+                        eq(userId)
+                    )
+                )
+                .thenReturn(listOf(resolveInfo))
+
+            val componentNames by collectLastValue(underTest.getInstalledTilesComponents(userId))
+
+            assertThat(componentNames).containsExactly(TEST_COMPONENT)
+        }
+
     private fun getRegisteredReceiver(): BroadcastReceiver {
         verify(context)
             .registerReceiverAsUser(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSTileImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSTileImplTest.java
index 962b537..22b1c7b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSTileImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSTileImplTest.java
@@ -204,6 +204,19 @@
     }
 
     @Test
+    public void testLongClick_falsing() {
+        mFalsingManager.setFalseLongTap(true);
+        mTile.longClick(null /* view */);
+        mTestableLooper.processAllMessages();
+        assertThat(mTile.mLongClicked).isFalse();
+
+        mFalsingManager.setFalseLongTap(false);
+        mTile.longClick(null /* view */);
+        mTestableLooper.processAllMessages();
+        assertThat(mTile.mLongClicked).isTrue();
+    }
+
+    @Test
     public void testSecondaryClick_Metrics() {
         mTile.secondaryClick(null /* view */);
         verify(mMetricsLogger).write(argThat(new TileLogMatcher(ACTION_QS_SECONDARY_CLICK)));
@@ -518,6 +531,7 @@
     }
     private static class TileImpl extends QSTileImpl<QSTile.BooleanState> {
         boolean mClicked;
+        boolean mLongClicked;
         int mRefreshes = 0;
 
         protected TileImpl(
@@ -551,6 +565,11 @@
         }
 
         @Override
+        protected void handleLongClick(@Nullable View view) {
+            mLongClicked = true;
+        }
+
+        @Override
         protected void handleUpdateState(BooleanState state, Object arg) {
             mRefreshes++;
         }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/settings/brightness/BrightnessDialogTest.kt b/packages/SystemUI/tests/src/com/android/systemui/settings/brightness/BrightnessDialogTest.kt
index 7646193..16751c9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/settings/brightness/BrightnessDialogTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/settings/brightness/BrightnessDialogTest.kt
@@ -16,7 +16,6 @@
 
 package com.android.systemui.settings.brightness
 
-import android.content.Intent
 import android.graphics.Rect
 import android.os.Handler
 import android.testing.AndroidTestingRunner
@@ -25,9 +24,9 @@
 import android.view.ViewGroup
 import androidx.test.filters.SmallTest
 import androidx.test.rule.ActivityTestRule
-import androidx.test.runner.intercepting.SingleActivityFactory
 import com.android.systemui.R
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.activity.SingleActivityFactory
 import com.android.systemui.settings.FakeDisplayTracker
 import com.android.systemui.settings.UserTracker
 import com.android.systemui.util.mockito.any
@@ -59,19 +58,17 @@
     @JvmField
     var activityRule =
         ActivityTestRule(
-            object : SingleActivityFactory<TestDialog>(TestDialog::class.java) {
-                override fun create(intent: Intent?): TestDialog {
-                    return TestDialog(
-                        userTracker,
-                        displayTracker,
-                        brightnessSliderControllerFactory,
-                        mainExecutor,
-                        backgroundHandler
-                    )
-                }
+            /* activityFactory= */ SingleActivityFactory {
+                TestDialog(
+                    userTracker,
+                    displayTracker,
+                    brightnessSliderControllerFactory,
+                    mainExecutor,
+                    backgroundHandler
+                )
             },
-            false,
-            false
+            /* initialTouchMode= */ false,
+            /* launchActivity= */ false,
         )
 
     @Before
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java
index 5802eb3..eb4ae1a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java
@@ -514,6 +514,17 @@
     }
 
     @Test
+    public void keyguardStatusView_willPlayDelayedDoze_notifiesKeyguardMediaController() {
+        when(mNotificationStackScrollLayoutController.getVisibleNotificationCount()).thenReturn(2);
+        mStatusBarStateController.setState(KEYGUARD);
+        enableSplitShade(/* enabled= */ true);
+
+        mNotificationPanelViewController.setWillPlayDelayedDozeAmountAnimation(true);
+
+        verify(mKeyguardMediaController).setDozeWakeUpAnimationWaiting(true);
+    }
+
+    @Test
     public void keyguardStatusView_willPlayDelayedDoze_isCentered_thenStillCenteredIfNoNotifs() {
         when(mNotificationStackScrollLayoutController.getVisibleNotificationCount()).thenReturn(0);
         mStatusBarStateController.setState(KEYGUARD);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt
index 2a9b403..5fb3a79 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt
@@ -28,6 +28,11 @@
 import com.android.systemui.R
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.back.domain.interactor.BackActionInteractor
+import com.android.systemui.bouncer.data.factory.BouncerMessageFactory
+import com.android.systemui.bouncer.data.repository.FakeBouncerMessageRepository
+import com.android.systemui.bouncer.domain.interactor.BouncerMessageInteractor
+import com.android.systemui.bouncer.domain.interactor.CountDownTimerUtil
+import com.android.systemui.bouncer.ui.viewmodel.KeyguardBouncerViewModel
 import com.android.systemui.classifier.FalsingCollectorFake
 import com.android.systemui.classifier.FalsingManagerFake
 import com.android.systemui.dock.DockManager
@@ -35,14 +40,9 @@
 import com.android.systemui.flags.FakeFeatureFlags
 import com.android.systemui.flags.Flags
 import com.android.systemui.keyguard.KeyguardUnlockAnimationController
-import com.android.systemui.bouncer.data.factory.BouncerMessageFactory
-import com.android.systemui.bouncer.domain.interactor.BouncerMessageInteractor
-import com.android.systemui.bouncer.domain.interactor.CountDownTimerUtil
-import com.android.systemui.bouncer.data.repository.FakeBouncerMessageRepository
-import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
 import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractorFactory
 import com.android.systemui.keyguard.shared.model.TransitionStep
-import com.android.systemui.bouncer.ui.viewmodel.KeyguardBouncerViewModel
 import com.android.systemui.keyguard.ui.viewmodel.PrimaryBouncerToGoneTransitionViewModel
 import com.android.systemui.log.BouncerLogger
 import com.android.systemui.multishade.data.remoteproxy.MultiShadeInputProxy
@@ -80,9 +80,9 @@
 import org.mockito.Mockito.mock
 import org.mockito.Mockito.never
 import org.mockito.Mockito.verify
-import org.mockito.Mockito.`when` as whenever
 import org.mockito.MockitoAnnotations
 import java.util.Optional
+import org.mockito.Mockito.`when` as whenever
 
 @OptIn(ExperimentalCoroutinesApi::class)
 @SmallTest
@@ -198,10 +198,9 @@
                         multiShadeInteractor = multiShadeInteractor,
                         featureFlags = featureFlags,
                         keyguardTransitionInteractor =
-                            KeyguardTransitionInteractor(
-                                repository = FakeKeyguardTransitionRepository(),
-                                scope = testScope.backgroundScope
-                            ),
+                            KeyguardTransitionInteractorFactory.create(
+                                    scope = TestScope().backgroundScope,
+                            ).keyguardTransitionInteractor,
                         falsingManager = FalsingManagerFake(),
                         shadeController = shadeController,
                     )
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt
index 252a03b..544137e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt
@@ -40,8 +40,8 @@
 import com.android.systemui.flags.FakeFeatureFlags
 import com.android.systemui.flags.Flags
 import com.android.systemui.keyguard.KeyguardUnlockAnimationController
-import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
 import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractorFactory
 import com.android.systemui.keyguard.ui.viewmodel.PrimaryBouncerToGoneTransitionViewModel
 import com.android.systemui.log.BouncerLogger
 import com.android.systemui.multishade.data.remoteproxy.MultiShadeInputProxy
@@ -211,10 +211,10 @@
                         multiShadeInteractor = multiShadeInteractor,
                         featureFlags = featureFlags,
                         keyguardTransitionInteractor =
-                            KeyguardTransitionInteractor(
-                                repository = FakeKeyguardTransitionRepository(),
-                                scope = testScope.backgroundScope
-                            ),
+                            KeyguardTransitionInteractorFactory.create(
+                                    scope = TestScope().backgroundScope,
+                                )
+                                .keyguardTransitionInteractor,
                         falsingManager = FalsingManagerFake(),
                         shadeController = shadeController,
                     )
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerBaseTest.java
index c72f4e7..a2c2912 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerBaseTest.java
@@ -169,6 +169,7 @@
         when(mDeviceProvisionedController.isDeviceProvisioned()).thenReturn(true);
         mShadeInteractor =
                 new ShadeInteractor(
+                        mTestScope.getBackgroundScope(),
                         mDisableFlagsRepository,
                         mKeyguardRepository,
                         new FakeUserSetupRepository(),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/data/repository/ShadeInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/data/repository/ShadeInteractorTest.kt
index 7392a94..3ea8f54 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/data/repository/ShadeInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/data/repository/ShadeInteractorTest.kt
@@ -84,6 +84,7 @@
         MockitoAnnotations.initMocks(this)
 
         featureFlags.set(Flags.FACE_AUTH_REFACTOR, false)
+        featureFlags.set(Flags.FULL_SCREEN_USER_SWITCHER, true)
 
         val refreshUsersScheduler =
             RefreshUsersScheduler(
@@ -117,6 +118,7 @@
             )
         underTest =
             ShadeInteractor(
+                testScope.backgroundScope,
                 disableFlagsRepository,
                 keyguardRepository,
                 userSetupRepository,
@@ -126,6 +128,20 @@
     }
 
     @Test
+    fun isShadeEnabled_matchesDisableFlagsRepo() =
+        testScope.runTest {
+            val actual by collectLastValue(underTest.isShadeEnabled)
+
+            disableFlagsRepository.disableFlags.value =
+                DisableFlagsModel(disable2 = DISABLE2_NOTIFICATION_SHADE)
+            assertThat(actual).isFalse()
+
+            disableFlagsRepository.disableFlags.value = DisableFlagsModel(disable2 = DISABLE2_NONE)
+
+            assertThat(actual).isTrue()
+        }
+
+    @Test
     fun isExpandToQsEnabled_deviceNotProvisioned_false() =
         testScope.runTest {
             whenever(deviceProvisionedController.isDeviceProvisioned).thenReturn(false)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/LockscreenShadeTransitionControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/LockscreenShadeTransitionControllerTest.kt
index 21e0f68..ff2f106 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/LockscreenShadeTransitionControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/LockscreenShadeTransitionControllerTest.kt
@@ -1,5 +1,6 @@
 package com.android.systemui.statusbar
 
+import android.app.StatusBarManager.DISABLE2_NOTIFICATION_SHADE
 import android.testing.AndroidTestingRunner
 import android.testing.TestableLooper
 import android.testing.TestableLooper.RunWithLooper
@@ -11,6 +12,7 @@
 import com.android.systemui.classifier.FalsingCollectorFake
 import com.android.systemui.dump.DumpManager
 import com.android.systemui.keyguard.WakefulnessLifecycle
+import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
 import com.android.systemui.media.controls.ui.MediaHierarchyManager
 import com.android.systemui.plugins.ActivityStarter
 import com.android.systemui.plugins.FalsingManager
@@ -19,6 +21,9 @@
 import com.android.systemui.power.domain.interactor.PowerInteractor
 import com.android.systemui.shade.ShadeViewController
 import com.android.systemui.shade.data.repository.FakeShadeRepository
+import com.android.systemui.shade.domain.interactor.ShadeInteractor
+import com.android.systemui.statusbar.disableflags.data.model.DisableFlagsModel
+import com.android.systemui.statusbar.disableflags.data.repository.FakeDisableFlagsRepository
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
 import com.android.systemui.statusbar.notification.row.NotificationTestHelper
 import com.android.systemui.statusbar.notification.stack.AmbientState
@@ -28,8 +33,13 @@
 import com.android.systemui.statusbar.phone.KeyguardBypassController
 import com.android.systemui.statusbar.phone.LSShadeTransitionLogger
 import com.android.systemui.statusbar.phone.ScrimController
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeUserSetupRepository
 import com.android.systemui.statusbar.policy.FakeConfigurationController
 import com.android.systemui.util.mockito.mock
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.StandardTestDispatcher
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runCurrent
 import org.junit.After
 import org.junit.Assert.assertFalse
 import org.junit.Assert.assertNotNull
@@ -60,8 +70,11 @@
 @SmallTest
 @RunWithLooper(setAsMainLooper = true)
 @RunWith(AndroidTestingRunner::class)
+@OptIn(ExperimentalCoroutinesApi::class)
 class LockscreenShadeTransitionControllerTest : SysuiTestCase() {
 
+    private val testScope = TestScope(StandardTestDispatcher())
+
     lateinit var transitionController: LockscreenShadeTransitionController
     lateinit var row: ExpandableNotificationRow
     @Mock lateinit var statusbarStateController: SysuiStatusBarStateController
@@ -87,6 +100,15 @@
     @Mock lateinit var qsTransitionController: LockscreenShadeQsTransitionController
     @Mock lateinit var activityStarter: ActivityStarter
     @Mock lateinit var transitionControllerCallback: LockscreenShadeTransitionController.Callback
+    private val disableFlagsRepository = FakeDisableFlagsRepository()
+    private val shadeInteractor = ShadeInteractor(
+        testScope.backgroundScope,
+        disableFlagsRepository,
+        keyguardRepository = FakeKeyguardRepository(),
+        userSetupRepository = FakeUserSetupRepository(),
+        deviceProvisionedController = mock(),
+        userInteractor = mock(),
+    )
     private val powerInteractor = PowerInteractor(
         FakePowerRepository(),
         FalsingCollectorFake(),
@@ -99,6 +121,10 @@
 
     @Before
     fun setup() {
+        // By default, have the shade enabled
+        disableFlagsRepository.disableFlags.value = DisableFlagsModel()
+        testScope.runCurrent()
+
         val helper = NotificationTestHelper(
                 mContext,
                 mDependency,
@@ -139,6 +165,7 @@
                 qsTransitionControllerFactory = { qsTransitionController },
                 activityStarter = activityStarter,
                 shadeRepository = FakeShadeRepository(),
+                shadeInteractor = shadeInteractor,
                 powerInteractor = powerInteractor,
             )
         transitionController.addCallback(transitionControllerCallback)
@@ -214,7 +241,10 @@
 
     @Test
     fun testDontGoWhenShadeDisabled() {
-        whenever(mCentralSurfaces.isShadeDisabled).thenReturn(true)
+        disableFlagsRepository.disableFlags.value = DisableFlagsModel(
+            disable2 = DISABLE2_NOTIFICATION_SHADE,
+        )
+        testScope.runCurrent()
         transitionController.goToLockedShade(null)
         verify(statusbarStateController, never()).setState(anyInt())
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/commandline/CommandParserTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/commandline/CommandParserTest.kt
new file mode 100644
index 0000000..cfbe8e3
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/commandline/CommandParserTest.kt
@@ -0,0 +1,212 @@
+/*
+ * 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.commandline
+
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.google.common.truth.Truth.assertThat
+import java.io.PrintWriter
+import org.junit.Assert.assertFalse
+import org.junit.Assert.assertThrows
+import org.junit.Assert.assertTrue
+import org.junit.Test
+
+@SmallTest
+class CommandParserTest : SysuiTestCase() {
+    private val parser = CommandParser()
+
+    @Test
+    fun registerToken_cannotReuseNames() {
+        parser.flag("-f")
+        assertThrows(IllegalArgumentException::class.java) { parser.flag("-f") }
+    }
+
+    @Test
+    fun unknownToken_throws() {
+        assertThrows(ArgParseError::class.java) { parser.parse(listOf("unknown-token")) }
+    }
+
+    @Test
+    fun parseSingleFlag_present() {
+        val flag by parser.flag("-f")
+        parser.parse(listOf("-f"))
+        assertTrue(flag)
+    }
+
+    @Test
+    fun parseSingleFlag_notPresent() {
+        val flag by parser.flag("-f")
+        parser.parse(listOf())
+        assertFalse(flag)
+    }
+
+    @Test
+    fun parseSingleOptionalParam_present() {
+        val param by parser.param("-p", valueParser = Type.Int)
+        parser.parse(listOf("-p", "123"))
+        assertThat(param).isEqualTo(123)
+    }
+
+    @Test
+    fun parseSingleOptionalParam_notPresent() {
+        val param by parser.param("-p", valueParser = Type.Int)
+        parser.parse(listOf())
+        assertThat(param).isNull()
+    }
+
+    @Test
+    fun parseSingleOptionalParam_missingArg_throws() {
+        val param by parser.param("-p", valueParser = Type.Int)
+        assertThrows(ArgParseError::class.java) { parser.parse(listOf("-p")) }
+    }
+
+    @Test
+    fun parseSingleRequiredParam_present() {
+        val param by parser.require(parser.param("-p", valueParser = Type.Int))
+        parser.parse(listOf("-p", "123"))
+        assertThat(param).isEqualTo(123)
+    }
+
+    @Test
+    fun parseSingleRequiredParam_notPresent_failsValidation() {
+        val param by parser.require(parser.param("-p", valueParser = Type.Int))
+        assertFalse(parser.parse(listOf()))
+    }
+
+    @Test
+    fun parseSingleRequiredParam_missingArg_throws() {
+        val param by parser.require(parser.param("-p", valueParser = Type.Int))
+        assertThrows(ArgParseError::class.java) { parser.parse(listOf("-p")) }
+    }
+
+    @Test
+    fun parseAsSubCommand_singleFlag_present() {
+        val flag by parser.flag("-f")
+        val args = listOf("-f").listIterator()
+        parser.parseAsSubCommand(args)
+
+        assertTrue(flag)
+    }
+
+    @Test
+    fun parseAsSubCommand_singleFlag_notPresent() {
+        val flag by parser.flag("-f")
+        val args = listOf("--other-flag").listIterator()
+        parser.parseAsSubCommand(args)
+
+        assertFalse(flag)
+    }
+
+    @Test
+    fun parseAsSubCommand_singleOptionalParam_present() {
+        val param by parser.param("-p", valueParser = Type.Int)
+        parser.parseAsSubCommand(listOf("-p", "123", "--other-arg", "321").listIterator())
+        assertThat(param).isEqualTo(123)
+    }
+
+    @Test
+    fun parseAsSubCommand_singleOptionalParam_notPresent() {
+        val param by parser.param("-p", valueParser = Type.Int)
+        parser.parseAsSubCommand(listOf("--other-arg", "321").listIterator())
+        assertThat(param).isNull()
+    }
+
+    @Test
+    fun parseAsSubCommand_singleRequiredParam_present() {
+        val param by parser.require(parser.param("-p", valueParser = Type.Int))
+        parser.parseAsSubCommand(listOf("-p", "123", "--other-arg", "321").listIterator())
+        assertThat(param).isEqualTo(123)
+    }
+
+    @Test
+    fun parseAsSubCommand_singleRequiredParam_notPresent() {
+        parser.require(parser.param("-p", valueParser = Type.Int))
+        assertFalse(parser.parseAsSubCommand(listOf("--other-arg", "321").listIterator()))
+    }
+
+    @Test
+    fun parseCommandWithSubCommand_required_provided() {
+        val topLevelFlag by parser.flag("flag", shortName = "-f")
+
+        val cmd =
+            object : ParseableCommand("test") {
+                val flag by flag("flag1")
+                override fun execute(pw: PrintWriter) {}
+            }
+
+        parser.require(parser.subCommand(cmd))
+        parser.parse(listOf("-f", "test", "--flag1"))
+
+        assertTrue(topLevelFlag)
+        assertThat(cmd).isNotNull()
+        assertTrue(cmd.flag)
+    }
+
+    @Test
+    fun parseCommandWithSubCommand_required_notProvided() {
+        val topLevelFlag by parser.flag("-f")
+
+        val cmd =
+            object : ParseableCommand("test") {
+                val flag by parser.flag("flag1")
+                override fun execute(pw: PrintWriter) {}
+            }
+
+        parser.require(parser.subCommand(cmd))
+
+        assertFalse(parser.parse(listOf("-f")))
+    }
+
+    @Test
+    fun flag_requiredParam_optionalParam_allProvided_failsValidation() {
+        val flag by parser.flag("-f")
+        val optionalParam by parser.param("-p", valueParser = Type.Int)
+        val requiredParam by parser.require(parser.param("-p2", valueParser = Type.Boolean))
+
+        parser.parse(
+            listOf(
+                "-f",
+                "-p",
+                "123",
+                "-p2",
+                "false",
+            )
+        )
+
+        assertTrue(flag)
+        assertThat(optionalParam).isEqualTo(123)
+        assertFalse(requiredParam)
+    }
+
+    @Test
+    fun flag_requiredParam_optionalParam_optionalExcluded() {
+        val flag by parser.flag("-f")
+        val optionalParam by parser.param("-p", valueParser = Type.Int)
+        val requiredParam by parser.require(parser.param("-p2", valueParser = Type.Boolean))
+
+        parser.parse(
+            listOf(
+                "-p2",
+                "true",
+            )
+        )
+
+        assertFalse(flag)
+        assertThat(optionalParam).isNull()
+        assertTrue(requiredParam)
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/commandline/ParametersTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/commandline/ParametersTest.kt
new file mode 100644
index 0000000..e391d6b
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/commandline/ParametersTest.kt
@@ -0,0 +1,55 @@
+package com.android.systemui.statusbar.commandline
+
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.google.common.truth.Truth.assertThat
+import org.junit.Assert.assertFalse
+import org.junit.Assert.assertThrows
+import org.junit.Assert.assertTrue
+import org.junit.Test
+
+@SmallTest
+class ParametersTest : SysuiTestCase() {
+    @Test
+    fun singleArgOptional_returnsNullBeforeParse() {
+        val optional by SingleArgParamOptional(longName = "longName", valueParser = Type.Int)
+        assertThat(optional).isNull()
+    }
+
+    @Test
+    fun singleArgOptional_returnsParsedValue() {
+        val param = SingleArgParamOptional(longName = "longName", valueParser = Type.Int)
+        param.parseArgsFromIter(listOf("3").listIterator())
+        val optional by param
+        assertThat(optional).isEqualTo(3)
+    }
+
+    @Test
+    fun singleArgRequired_throwsBeforeParse() {
+        val req by SingleArgParam(longName = "param", valueParser = Type.Boolean)
+        assertThrows(IllegalStateException::class.java) { req }
+    }
+
+    @Test
+    fun singleArgRequired_returnsParsedValue() {
+        val param = SingleArgParam(longName = "param", valueParser = Type.Boolean)
+        param.parseArgsFromIter(listOf("true").listIterator())
+        val req by param
+        assertTrue(req)
+    }
+
+    @Test
+    fun param_handledAfterParse() {
+        val optParam = SingleArgParamOptional(longName = "string1", valueParser = Type.String)
+        val reqParam = SingleArgParam(longName = "string2", valueParser = Type.Float)
+
+        assertFalse(optParam.handled)
+        assertFalse(reqParam.handled)
+
+        optParam.parseArgsFromIter(listOf("test").listIterator())
+        reqParam.parseArgsFromIter(listOf("1.23").listIterator())
+
+        assertTrue(optParam.handled)
+        assertTrue(reqParam.handled)
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/commandline/ParseableCommandTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/commandline/ParseableCommandTest.kt
new file mode 100644
index 0000000..86548d0
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/commandline/ParseableCommandTest.kt
@@ -0,0 +1,317 @@
+/*
+ * 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.commandline
+
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.google.common.truth.Truth.assertThat
+import java.io.PrintWriter
+import org.junit.Assert.assertThrows
+import org.junit.Assert.assertTrue
+import org.junit.Before
+import org.junit.Test
+import org.mockito.Mock
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+class ParseableCommandTest : SysuiTestCase() {
+    @Mock private lateinit var pw: PrintWriter
+
+    @Before
+    fun setup() {
+        MockitoAnnotations.initMocks(this)
+    }
+
+    /**
+     * A little change-detector-y, but this is just a general assertion that building up a command
+     * parser via its wrapper works as expected.
+     */
+    @Test
+    fun testFactoryMethods() {
+        val mySubCommand =
+            object : ParseableCommand("subCommand") {
+                val flag by flag("flag")
+                override fun execute(pw: PrintWriter) {}
+            }
+
+        val mySubCommand2 =
+            object : ParseableCommand("subCommand2") {
+                val flag by flag("flag")
+                override fun execute(pw: PrintWriter) {}
+            }
+
+        // Verify that the underlying parser contains the correct types
+        val myCommand =
+            object : ParseableCommand("testName") {
+                val flag by flag("flag", shortName = "f")
+                val requiredParam by
+                    param(longName = "required-param", shortName = "r", valueParser = Type.String)
+                        .required()
+                val optionalParam by
+                    param(longName = "optional-param", shortName = "o", valueParser = Type.Boolean)
+                val optionalSubCommand by subCommand(mySubCommand)
+                val requiredSubCommand by subCommand(mySubCommand2).required()
+
+                override fun execute(pw: PrintWriter) {}
+            }
+
+        val flags = myCommand.parser.flags
+        val params = myCommand.parser.params
+        val subCommands = myCommand.parser.subCommands
+
+        assertThat(flags).hasSize(2)
+        assertThat(flags[0]).isInstanceOf(Flag::class.java)
+        assertThat(flags[1]).isInstanceOf(Flag::class.java)
+
+        assertThat(params).hasSize(2)
+        val req = params.filter { it is SingleArgParam<*> }
+        val opt = params.filter { it is SingleArgParamOptional<*> }
+        assertThat(req).hasSize(1)
+        assertThat(opt).hasSize(1)
+
+        val reqSub = subCommands.filter { it is RequiredSubCommand<*> }
+        val optSub = subCommands.filter { it is OptionalSubCommand<*> }
+        assertThat(reqSub).hasSize(1)
+        assertThat(optSub).hasSize(1)
+    }
+
+    @Test
+    fun factoryMethods_enforceShortNameRules() {
+        // Short names MUST be one character long
+        assertThrows(IllegalArgumentException::class.java) {
+            val myCommand =
+                object : ParseableCommand("test-command") {
+                    val flag by flag("longName", "invalidShortName")
+
+                    override fun execute(pw: PrintWriter) {}
+                }
+        }
+
+        assertThrows(IllegalArgumentException::class.java) {
+            val myCommand =
+                object : ParseableCommand("test-command") {
+                    val param by param("longName", "invalidShortName", valueParser = Type.String)
+
+                    override fun execute(pw: PrintWriter) {}
+                }
+        }
+    }
+
+    @Test
+    fun factoryMethods_enforceLongNames_notPrefixed() {
+        // Long names must not start with "-", since they will be added
+        assertThrows(IllegalArgumentException::class.java) {
+            val myCommand =
+                object : ParseableCommand("test-command") {
+                    val flag by flag("--invalid")
+
+                    override fun execute(pw: PrintWriter) {}
+                }
+        }
+
+        assertThrows(IllegalArgumentException::class.java) {
+            val myCommand =
+                object : ParseableCommand("test-command") {
+                    val param by param("-invalid", valueParser = Type.String)
+
+                    override fun execute(pw: PrintWriter) {}
+                }
+        }
+    }
+
+    @Test
+    fun executeDoesNotPropagateExceptions() {
+        val cmd =
+            object : ParseableCommand("test-command") {
+                val flag by flag("flag")
+                override fun execute(pw: PrintWriter) {}
+            }
+
+        val throwingCommand = listOf("unknown-token")
+
+        // Given a command that would cause an ArgParseError
+        assertThrows(ArgParseError::class.java) { cmd.parser.parse(throwingCommand) }
+
+        // The parser consumes that error
+        cmd.execute(pw, throwingCommand)
+    }
+
+    @Test
+    fun executeFailingCommand_callsOnParseFailed() {
+        val cmd =
+            object : ParseableCommand("test-command") {
+                val flag by flag("flag")
+
+                var onParseFailedCalled = false
+
+                override fun execute(pw: PrintWriter) {}
+                override fun onParseFailed(error: ArgParseError) {
+                    onParseFailedCalled = true
+                }
+            }
+
+        val throwingCommand = listOf("unknown-token")
+        cmd.execute(pw, throwingCommand)
+
+        assertTrue(cmd.onParseFailedCalled)
+    }
+
+    @Test
+    fun baseCommand() {
+        val myCommand = MyCommand()
+        myCommand.execute(pw, baseCommand)
+
+        assertThat(myCommand.flag1).isFalse()
+        assertThat(myCommand.singleParam).isNull()
+    }
+
+    @Test
+    fun commandWithFlags() {
+        val command = MyCommand()
+        command.execute(pw, cmdWithFlags)
+
+        assertThat(command.flag1).isTrue()
+        assertThat(command.flag2).isTrue()
+    }
+
+    @Test
+    fun commandWithArgs() {
+        val cmd = MyCommand()
+        cmd.execute(pw, cmdWithSingleArgParam)
+
+        assertThat(cmd.singleParam).isEqualTo("single_param")
+    }
+
+    @Test
+    fun commandWithRequiredParam_provided() {
+        val cmd =
+            object : ParseableCommand(name) {
+                val singleRequiredParam: String by
+                    param(
+                            longName = "param1",
+                            shortName = "p",
+                            valueParser = Type.String,
+                        )
+                        .required()
+
+                override fun execute(pw: PrintWriter) {}
+            }
+
+        val cli = listOf("-p", "value")
+        cmd.execute(pw, cli)
+
+        assertThat(cmd.singleRequiredParam).isEqualTo("value")
+    }
+
+    @Test
+    fun commandWithRequiredParam_not_provided_throws() {
+        val cmd =
+            object : ParseableCommand(name) {
+                val singleRequiredParam by
+                    param(shortName = "p", longName = "param1", valueParser = Type.String)
+                        .required()
+
+                override fun execute(pw: PrintWriter) {}
+
+                override fun execute(pw: PrintWriter, args: List<String>) {
+                    parser.parse(args)
+                    execute(pw)
+                }
+            }
+
+        val cli = listOf("")
+        assertThrows(ArgParseError::class.java) { cmd.execute(pw, cli) }
+    }
+
+    @Test
+    fun commandWithSubCommand() {
+        val subName = "sub-command"
+        val subCmd =
+            object : ParseableCommand(subName) {
+                val singleOptionalParam: String? by param("param", valueParser = Type.String)
+
+                override fun execute(pw: PrintWriter) {}
+            }
+
+        val cmd =
+            object : ParseableCommand(name) {
+                val subCmd by subCommand(subCmd)
+                override fun execute(pw: PrintWriter) {}
+            }
+
+        cmd.execute(pw, listOf("sub-command", "--param", "test"))
+        assertThat(cmd.subCmd?.singleOptionalParam).isEqualTo("test")
+    }
+
+    @Test
+    fun complexCommandWithSubCommands_reusedNames() {
+        val commandLine = "-f --param1 arg1 sub-command1 -f -p arg2 --param2 arg3".split(" ")
+
+        val subName = "sub-command1"
+        val subCmd =
+            object : ParseableCommand(subName) {
+                val flag1 by flag("flag", shortName = "f")
+                val param1: String? by param("param1", shortName = "p", valueParser = Type.String)
+
+                override fun execute(pw: PrintWriter) {}
+            }
+
+        val myCommand =
+            object : ParseableCommand(name) {
+                val flag1 by flag(longName = "flag", shortName = "f")
+                val param1 by param("param1", shortName = "p", valueParser = Type.String).required()
+                val param2: String? by param(longName = "param2", valueParser = Type.String)
+                val subCommand by subCommand(subCmd)
+
+                override fun execute(pw: PrintWriter) {}
+            }
+
+        myCommand.execute(pw, commandLine)
+
+        assertThat(myCommand.flag1).isTrue()
+        assertThat(myCommand.param1).isEqualTo("arg1")
+        assertThat(myCommand.param2).isEqualTo("arg3")
+        assertThat(myCommand.subCommand).isNotNull()
+        assertThat(myCommand.subCommand?.flag1).isTrue()
+        assertThat(myCommand.subCommand?.param1).isEqualTo("arg2")
+    }
+
+    class MyCommand(
+        private val onExecute: ((MyCommand) -> Unit)? = null,
+    ) : ParseableCommand(name) {
+
+        val flag1 by flag(shortName = "f", longName = "flag1", description = "flag 1 for test")
+        val flag2 by flag(shortName = "g", longName = "flag2", description = "flag 2 for test")
+        val singleParam: String? by
+            param(
+                shortName = "a",
+                longName = "arg1",
+                valueParser = Type.String,
+            )
+
+        override fun execute(pw: PrintWriter) {
+            onExecute?.invoke(this)
+        }
+    }
+
+    companion object {
+        const val name = "my_command"
+        val baseCommand = listOf("")
+        val cmdWithFlags = listOf("-f", "--flag2")
+        val cmdWithSingleArgParam = listOf("--arg1", "single_param")
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/commandline/ValueParserTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/commandline/ValueParserTest.kt
new file mode 100644
index 0000000..759f0bc
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/commandline/ValueParserTest.kt
@@ -0,0 +1,61 @@
+package com.android.systemui.statusbar.commandline
+
+import android.graphics.Rect
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.google.common.truth.Truth.assertThat
+import org.junit.Assert.assertTrue
+import org.junit.Test
+
+@SmallTest
+class ValueParserTest : SysuiTestCase() {
+    @Test
+    fun parseString() {
+        assertThat(Type.String.parseValue("test")).isEqualTo(Result.success("test"))
+    }
+
+    @Test
+    fun parseInt() {
+        assertThat(Type.Int.parseValue("123")).isEqualTo(Result.success(123))
+
+        assertTrue(Type.Int.parseValue("not an Int").isFailure)
+    }
+
+    @Test
+    fun parseFloat() {
+        assertThat(Type.Float.parseValue("1.23")).isEqualTo(Result.success(1.23f))
+
+        assertTrue(Type.Int.parseValue("not a Float").isFailure)
+    }
+
+    @Test
+    fun parseBoolean() {
+        assertThat(Type.Boolean.parseValue("true")).isEqualTo(Result.success(true))
+        assertThat(Type.Boolean.parseValue("false")).isEqualTo(Result.success(false))
+
+        assertTrue(Type.Boolean.parseValue("not a Boolean").isFailure)
+    }
+
+    @Test
+    fun mapToComplexType() {
+        val parseSquare = Type.Int.map { Rect(it, it, it, it) }
+
+        assertThat(parseSquare.parseValue("10")).isEqualTo(Result.success(Rect(10, 10, 10, 10)))
+    }
+
+    @Test
+    fun mapToFallibleComplexType() {
+        val fallibleParseSquare =
+            Type.Int.map {
+                if (it > 0) {
+                    Rect(it, it, it, it)
+                } else {
+                    null
+                }
+            }
+
+        assertThat(fallibleParseSquare.parseValue("10"))
+            .isEqualTo(Result.success(Rect(10, 10, 10, 10)))
+        assertTrue(fallibleParseSquare.parseValue("-10").isFailure)
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinatorTest.kt
index 2fbe871..ea70e9e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinatorTest.kt
@@ -32,7 +32,6 @@
 import com.android.systemui.keyguard.shared.model.TransitionStep
 import com.android.systemui.plugins.statusbar.StatusBarStateController
 import com.android.systemui.statusbar.StatusBarState
-import com.android.systemui.statusbar.notification.NotifPipelineFlags
 import com.android.systemui.statusbar.notification.collection.GroupEntryBuilder
 import com.android.systemui.statusbar.notification.collection.NotifPipeline
 import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder
@@ -46,11 +45,14 @@
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
 import com.android.systemui.statusbar.policy.HeadsUpManager
 import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener
+import com.android.systemui.util.mockito.any
 import com.android.systemui.util.mockito.eq
 import com.android.systemui.util.mockito.mock
 import com.android.systemui.util.mockito.withArgCaptor
 import com.android.systemui.util.settings.FakeSettings
 import com.google.common.truth.Truth.assertThat
+import java.util.function.Consumer
+import kotlin.time.Duration.Companion.seconds
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.test.TestCoroutineScheduler
@@ -62,9 +64,8 @@
 import org.mockito.ArgumentMatchers.same
 import org.mockito.Mockito.anyString
 import org.mockito.Mockito.clearInvocations
+import org.mockito.Mockito.never
 import org.mockito.Mockito.verify
-import java.util.function.Consumer
-import kotlin.time.Duration.Companion.seconds
 import org.mockito.Mockito.`when` as whenever
 
 @SmallTest
@@ -75,7 +76,6 @@
     private val keyguardNotifVisibilityProvider: KeyguardNotificationVisibilityProvider = mock()
     private val keyguardRepository = FakeKeyguardRepository()
     private val keyguardTransitionRepository = FakeKeyguardTransitionRepository()
-    private val notifPipelineFlags: NotifPipelineFlags = mock()
     private val notifPipeline: NotifPipeline = mock()
     private val sectionHeaderVisibilityProvider: SectionHeaderVisibilityProvider = mock()
     private val statusBarStateController: StatusBarStateController = mock()
@@ -136,13 +136,8 @@
             )
             testScheduler.runCurrent()
 
-            // WHEN: The shade is expanded
-            whenever(statusBarStateController.isExpanded).thenReturn(true)
-            statusBarStateListener.onExpandedChanged(true)
-            testScheduler.runCurrent()
-
-            // THEN: The notification is still treated as "unseen" and is not filtered out.
-            assertThat(unseenFilter.shouldFilterOut(fakeEntry, 0L)).isFalse()
+            // THEN: We are no longer listening for shade expansions
+            verify(statusBarStateController, never()).addCallback(any())
         }
     }
 
@@ -152,6 +147,10 @@
         keyguardRepository.setKeyguardShowing(false)
         whenever(statusBarStateController.isExpanded).thenReturn(false)
         runKeyguardCoordinatorTest {
+            keyguardTransitionRepository.sendTransitionStep(
+                TransitionStep(from = KeyguardState.LOCKSCREEN, to = KeyguardState.GONE)
+            )
+
             // WHEN: A notification is posted
             val fakeEntry = NotificationEntryBuilder().build()
             collectionListener.onEntryAdded(fakeEntry)
@@ -162,6 +161,9 @@
 
             // WHEN: The keyguard is now showing
             keyguardRepository.setKeyguardShowing(true)
+            keyguardTransitionRepository.sendTransitionStep(
+                TransitionStep(from = KeyguardState.GONE, to = KeyguardState.AOD)
+            )
             testScheduler.runCurrent()
 
             // THEN: The notification is recognized as "seen" and is filtered out.
@@ -169,6 +171,9 @@
 
             // WHEN: The keyguard goes away
             keyguardRepository.setKeyguardShowing(false)
+            keyguardTransitionRepository.sendTransitionStep(
+                TransitionStep(from = KeyguardState.AOD, to = KeyguardState.GONE)
+            )
             testScheduler.runCurrent()
 
             // THEN: The notification is shown regardless
@@ -182,9 +187,10 @@
         keyguardRepository.setKeyguardShowing(false)
         whenever(statusBarStateController.isExpanded).thenReturn(true)
         runKeyguardCoordinatorTest {
-            val fakeEntry = NotificationEntryBuilder()
+            val fakeEntry =
+                NotificationEntryBuilder()
                     .setNotification(Notification.Builder(mContext, "id").setOngoing(true).build())
-                .build()
+                    .build()
             collectionListener.onEntryAdded(fakeEntry)
 
             // WHEN: The keyguard is now showing
@@ -202,11 +208,13 @@
         keyguardRepository.setKeyguardShowing(false)
         whenever(statusBarStateController.isExpanded).thenReturn(true)
         runKeyguardCoordinatorTest {
-            val fakeEntry = NotificationEntryBuilder().build().apply {
-                row = mock<ExpandableNotificationRow>().apply {
-                    whenever(isMediaRow).thenReturn(true)
+            val fakeEntry =
+                NotificationEntryBuilder().build().apply {
+                    row =
+                        mock<ExpandableNotificationRow>().apply {
+                            whenever(isMediaRow).thenReturn(true)
+                        }
                 }
-            }
             collectionListener.onEntryAdded(fakeEntry)
 
             // WHEN: The keyguard is now showing
@@ -299,14 +307,12 @@
         runKeyguardCoordinatorTest {
             // WHEN: A new notification is posted
             val fakeSummary = NotificationEntryBuilder().build()
-            val fakeChild = NotificationEntryBuilder()
+            val fakeChild =
+                NotificationEntryBuilder()
                     .setGroup(context, "group")
                     .setGroupSummary(context, false)
                     .build()
-            GroupEntryBuilder()
-                    .setSummary(fakeSummary)
-                    .addChild(fakeChild)
-                    .build()
+            GroupEntryBuilder().setSummary(fakeSummary).addChild(fakeChild).build()
 
             collectionListener.onEntryAdded(fakeSummary)
             collectionListener.onEntryAdded(fakeChild)
@@ -331,6 +337,10 @@
         runKeyguardCoordinatorTest {
             val fakeEntry = NotificationEntryBuilder().build()
             collectionListener.onEntryAdded(fakeEntry)
+            keyguardTransitionRepository.sendTransitionStep(
+                TransitionStep(from = KeyguardState.AOD, to = KeyguardState.LOCKSCREEN)
+            )
+            testScheduler.runCurrent()
 
             // WHEN: five seconds have passed
             testScheduler.advanceTimeBy(5.seconds)
@@ -338,10 +348,16 @@
 
             // WHEN: Keyguard is no longer showing
             keyguardRepository.setKeyguardShowing(false)
+            keyguardTransitionRepository.sendTransitionStep(
+                TransitionStep(from = KeyguardState.LOCKSCREEN, to = KeyguardState.GONE)
+            )
             testScheduler.runCurrent()
 
             // WHEN: Keyguard is shown again
             keyguardRepository.setKeyguardShowing(true)
+            keyguardTransitionRepository.sendTransitionStep(
+                TransitionStep(from = KeyguardState.GONE, to = KeyguardState.AOD)
+            )
             testScheduler.runCurrent()
 
             // THEN: The notification is now recognized as "seen" and is filtered out.
@@ -354,11 +370,17 @@
         // GIVEN: Keyguard is showing, unseen notification is present
         keyguardRepository.setKeyguardShowing(true)
         runKeyguardCoordinatorTest {
+            keyguardTransitionRepository.sendTransitionStep(
+                TransitionStep(from = KeyguardState.GONE, to = KeyguardState.LOCKSCREEN)
+            )
             val fakeEntry = NotificationEntryBuilder().build()
             collectionListener.onEntryAdded(fakeEntry)
 
             // WHEN: Keyguard is no longer showing
             keyguardRepository.setKeyguardShowing(false)
+            keyguardTransitionRepository.sendTransitionStep(
+                TransitionStep(from = KeyguardState.LOCKSCREEN, to = KeyguardState.GONE)
+            )
 
             // WHEN: Keyguard is shown again
             keyguardRepository.setKeyguardShowing(true)
@@ -369,14 +391,212 @@
         }
     }
 
+    @Test
+    fun unseenNotificationIsNotMarkedAsSeenIfNotOnKeyguardLongEnough() {
+        // GIVEN: Keyguard is showing, not dozing, unseen notification is present
+        keyguardRepository.setKeyguardShowing(true)
+        keyguardRepository.setIsDozing(false)
+        runKeyguardCoordinatorTest {
+            keyguardTransitionRepository.sendTransitionStep(
+                TransitionStep(from = KeyguardState.GONE, to = KeyguardState.LOCKSCREEN)
+            )
+            val firstEntry = NotificationEntryBuilder().setId(1).build()
+            collectionListener.onEntryAdded(firstEntry)
+            testScheduler.runCurrent()
+
+            // WHEN: one second has passed
+            testScheduler.advanceTimeBy(1.seconds)
+            testScheduler.runCurrent()
+
+            // WHEN: another unseen notification is posted
+            val secondEntry = NotificationEntryBuilder().setId(2).build()
+            collectionListener.onEntryAdded(secondEntry)
+            testScheduler.runCurrent()
+
+            // WHEN: four more seconds have passed
+            testScheduler.advanceTimeBy(4.seconds)
+            testScheduler.runCurrent()
+
+            // WHEN: the keyguard is no longer showing
+            keyguardRepository.setKeyguardShowing(false)
+            keyguardTransitionRepository.sendTransitionStep(
+                TransitionStep(from = KeyguardState.LOCKSCREEN, to = KeyguardState.GONE)
+            )
+            testScheduler.runCurrent()
+
+            // WHEN: Keyguard is shown again
+            keyguardRepository.setKeyguardShowing(true)
+            keyguardTransitionRepository.sendTransitionStep(
+                TransitionStep(from = KeyguardState.GONE, to = KeyguardState.LOCKSCREEN)
+            )
+            testScheduler.runCurrent()
+
+            // THEN: The first notification is considered seen and is filtered out.
+            assertThat(unseenFilter.shouldFilterOut(firstEntry, 0L)).isTrue()
+
+            // THEN: The second notification is still considered unseen and is not filtered out
+            assertThat(unseenFilter.shouldFilterOut(secondEntry, 0L)).isFalse()
+        }
+    }
+
+    @Test
+    fun unseenNotificationOnKeyguardNotMarkedAsSeenIfRemovedAfterThreshold() {
+        // GIVEN: Keyguard is showing, not dozing
+        keyguardRepository.setKeyguardShowing(true)
+        keyguardRepository.setIsDozing(false)
+        runKeyguardCoordinatorTest {
+            keyguardTransitionRepository.sendTransitionStep(
+                TransitionStep(from = KeyguardState.GONE, to = KeyguardState.LOCKSCREEN)
+            )
+            testScheduler.runCurrent()
+
+            // WHEN: a new notification is posted
+            val entry = NotificationEntryBuilder().setId(1).build()
+            collectionListener.onEntryAdded(entry)
+            testScheduler.runCurrent()
+
+            // WHEN: five more seconds have passed
+            testScheduler.advanceTimeBy(5.seconds)
+            testScheduler.runCurrent()
+
+            // WHEN: the notification is removed
+            collectionListener.onEntryRemoved(entry, 0)
+            testScheduler.runCurrent()
+
+            // WHEN: the notification is re-posted
+            collectionListener.onEntryAdded(entry)
+            testScheduler.runCurrent()
+
+            // WHEN: one more second has passed
+            testScheduler.advanceTimeBy(1.seconds)
+            testScheduler.runCurrent()
+
+            // WHEN: the keyguard is no longer showing
+            keyguardRepository.setKeyguardShowing(false)
+            keyguardTransitionRepository.sendTransitionStep(
+                TransitionStep(from = KeyguardState.LOCKSCREEN, to = KeyguardState.GONE)
+            )
+            testScheduler.runCurrent()
+
+            // WHEN: Keyguard is shown again
+            keyguardRepository.setKeyguardShowing(true)
+            keyguardTransitionRepository.sendTransitionStep(
+                TransitionStep(from = KeyguardState.GONE, to = KeyguardState.LOCKSCREEN)
+            )
+            testScheduler.runCurrent()
+
+            // THEN: The notification is considered unseen and is not filtered out.
+            assertThat(unseenFilter.shouldFilterOut(entry, 0L)).isFalse()
+        }
+    }
+
+    @Test
+    fun unseenNotificationOnKeyguardNotMarkedAsSeenIfRemovedBeforeThreshold() {
+        // GIVEN: Keyguard is showing, not dozing
+        keyguardRepository.setKeyguardShowing(true)
+        keyguardRepository.setIsDozing(false)
+        runKeyguardCoordinatorTest {
+            keyguardTransitionRepository.sendTransitionStep(
+                TransitionStep(from = KeyguardState.GONE, to = KeyguardState.LOCKSCREEN)
+            )
+            testScheduler.runCurrent()
+
+            // WHEN: a new notification is posted
+            val entry = NotificationEntryBuilder().setId(1).build()
+            collectionListener.onEntryAdded(entry)
+            testScheduler.runCurrent()
+
+            // WHEN: one second has passed
+            testScheduler.advanceTimeBy(1.seconds)
+            testScheduler.runCurrent()
+
+            // WHEN: the notification is removed
+            collectionListener.onEntryRemoved(entry, 0)
+            testScheduler.runCurrent()
+
+            // WHEN: the notification is re-posted
+            collectionListener.onEntryAdded(entry)
+            testScheduler.runCurrent()
+
+            // WHEN: one more second has passed
+            testScheduler.advanceTimeBy(1.seconds)
+            testScheduler.runCurrent()
+
+            // WHEN: the keyguard is no longer showing
+            keyguardRepository.setKeyguardShowing(false)
+            keyguardTransitionRepository.sendTransitionStep(
+                TransitionStep(from = KeyguardState.LOCKSCREEN, to = KeyguardState.GONE)
+            )
+            testScheduler.runCurrent()
+
+            // WHEN: Keyguard is shown again
+            keyguardRepository.setKeyguardShowing(true)
+            keyguardTransitionRepository.sendTransitionStep(
+                TransitionStep(from = KeyguardState.GONE, to = KeyguardState.LOCKSCREEN)
+            )
+            testScheduler.runCurrent()
+
+            // THEN: The notification is considered unseen and is not filtered out.
+            assertThat(unseenFilter.shouldFilterOut(entry, 0L)).isFalse()
+        }
+    }
+
+    @Test
+    fun unseenNotificationOnKeyguardNotMarkedAsSeenIfUpdatedBeforeThreshold() {
+        // GIVEN: Keyguard is showing, not dozing
+        keyguardRepository.setKeyguardShowing(true)
+        keyguardRepository.setIsDozing(false)
+        runKeyguardCoordinatorTest {
+            keyguardTransitionRepository.sendTransitionStep(
+                TransitionStep(from = KeyguardState.GONE, to = KeyguardState.LOCKSCREEN)
+            )
+            testScheduler.runCurrent()
+
+            // WHEN: a new notification is posted
+            val entry = NotificationEntryBuilder().setId(1).build()
+            collectionListener.onEntryAdded(entry)
+            testScheduler.runCurrent()
+
+            // WHEN: one second has passed
+            testScheduler.advanceTimeBy(1.seconds)
+            testScheduler.runCurrent()
+
+            // WHEN: the notification is updated
+            collectionListener.onEntryUpdated(entry)
+            testScheduler.runCurrent()
+
+            // WHEN: four more seconds have passed
+            testScheduler.advanceTimeBy(4.seconds)
+            testScheduler.runCurrent()
+
+            // WHEN: the keyguard is no longer showing
+            keyguardRepository.setKeyguardShowing(false)
+            keyguardTransitionRepository.sendTransitionStep(
+                TransitionStep(from = KeyguardState.LOCKSCREEN, to = KeyguardState.GONE)
+            )
+            testScheduler.runCurrent()
+
+            // WHEN: Keyguard is shown again
+            keyguardRepository.setKeyguardShowing(true)
+            keyguardTransitionRepository.sendTransitionStep(
+                TransitionStep(from = KeyguardState.GONE, to = KeyguardState.LOCKSCREEN)
+            )
+            testScheduler.runCurrent()
+
+            // THEN: The notification is considered unseen and is not filtered out.
+            assertThat(unseenFilter.shouldFilterOut(entry, 0L)).isFalse()
+        }
+    }
+
     private fun runKeyguardCoordinatorTest(
         testBlock: suspend KeyguardCoordinatorTestScope.() -> Unit
     ) {
         val testDispatcher = UnconfinedTestDispatcher()
         val testScope = TestScope(testDispatcher)
-        val fakeSettings = FakeSettings().apply {
-            putInt(Settings.Secure.LOCK_SCREEN_SHOW_ONLY_UNSEEN_NOTIFICATIONS, 1)
-        }
+        val fakeSettings =
+            FakeSettings().apply {
+                putInt(Settings.Secure.LOCK_SCREEN_SHOW_ONLY_UNSEEN_NOTIFICATIONS, 1)
+            }
         val seenNotificationsProvider = SeenNotificationsProviderImpl()
         val keyguardCoordinator =
             KeyguardCoordinator(
@@ -387,7 +607,6 @@
                 keyguardRepository,
                 keyguardTransitionRepository,
                 mock<KeyguardCoordinatorLogger>(),
-                notifPipelineFlags,
                 testScope.backgroundScope,
                 sectionHeaderVisibilityProvider,
                 fakeSettings,
@@ -397,11 +616,12 @@
         keyguardCoordinator.attach(notifPipeline)
         testScope.runTest(dispatchTimeoutMs = 1.seconds.inWholeMilliseconds) {
             KeyguardCoordinatorTestScope(
-                keyguardCoordinator,
-                testScope,
-                seenNotificationsProvider,
-                fakeSettings,
-            ).testBlock()
+                    keyguardCoordinator,
+                    testScope,
+                    seenNotificationsProvider,
+                    fakeSettings,
+                )
+                .testBlock()
         }
     }
 
@@ -414,10 +634,9 @@
         val testScheduler: TestCoroutineScheduler
             get() = scope.testScheduler
 
-        val onStateChangeListener: Consumer<String> =
-            withArgCaptor {
-                verify(keyguardNotifVisibilityProvider).addOnStateChangedListener(capture())
-            }
+        val onStateChangeListener: Consumer<String> = withArgCaptor {
+            verify(keyguardNotifVisibilityProvider).addOnStateChangedListener(capture())
+        }
 
         val unseenFilter: NotifFilter
             get() = keyguardCoordinator.unseenNotifFilter
@@ -426,11 +645,11 @@
             verify(notifPipeline).addCollectionListener(capture())
         }
 
-        val onHeadsUpChangedListener: OnHeadsUpChangedListener get() =
-            withArgCaptor { verify(headsUpManager).addListener(capture()) }
+        val onHeadsUpChangedListener: OnHeadsUpChangedListener
+            get() = withArgCaptor { verify(headsUpManager).addListener(capture()) }
 
-        val statusBarStateListener: StatusBarStateController.StateListener get() =
-            withArgCaptor { verify(statusBarStateController).addCallback(capture()) }
+        val statusBarStateListener: StatusBarStateController.StateListener
+            get() = withArgCaptor { verify(statusBarStateController).addCallback(capture()) }
 
         var showOnlyUnseenNotifsOnKeyguardSetting: Boolean
             get() =
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java
index 6ae7dca..ee8325e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java
@@ -87,6 +87,7 @@
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
 import com.android.systemui.statusbar.notification.row.NotificationGutsManager;
 import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController.NotificationPanelEvent;
+import com.android.systemui.statusbar.notification.stack.NotificationSwipeHelper.NotificationCallback;
 import com.android.systemui.statusbar.notification.stack.ui.viewmodel.NotificationListViewModel;
 import com.android.systemui.statusbar.phone.HeadsUpManagerPhone;
 import com.android.systemui.statusbar.phone.KeyguardBypassController;
@@ -415,6 +416,36 @@
     }
 
     @Test
+    public void callSwipeCallbacksDuringClearAll() {
+        initController(/* viewIsAttached= */ true);
+        ExpandableNotificationRow row = mock(ExpandableNotificationRow.class);
+        NotificationCallback notificationCallback = mController.mNotificationCallback;
+
+        when(mNotificationStackScrollLayout.getClearAllInProgress()).thenReturn(true);
+
+        notificationCallback.onBeginDrag(row);
+        verify(mNotificationStackScrollLayout).onSwipeBegin(row);
+
+        notificationCallback.handleChildViewDismissed(row);
+        verify(mNotificationStackScrollLayout).onSwipeEnd();
+    }
+
+    @Test
+    public void callSwipeCallbacksDuringClearNotification() {
+        initController(/* viewIsAttached= */ true);
+        ExpandableNotificationRow row = mock(ExpandableNotificationRow.class);
+        NotificationCallback notificationCallback = mController.mNotificationCallback;
+
+        when(mNotificationStackScrollLayout.getClearAllInProgress()).thenReturn(false);
+
+        notificationCallback.onBeginDrag(row);
+        verify(mNotificationStackScrollLayout).onSwipeBegin(row);
+
+        notificationCallback.handleChildViewDismissed(row);
+        verify(mNotificationStackScrollLayout).onSwipeEnd();
+    }
+
+    @Test
     public void testOnMenuClickedLogging() {
         ExpandableNotificationRow row = mock(ExpandableNotificationRow.class, RETURNS_DEEP_STUBS);
         when(row.getEntry().getSbn().getLogMaker()).thenReturn(new LogMaker(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacksTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacksTest.java
index 036b8be..8545b89 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacksTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacksTest.java
@@ -20,6 +20,7 @@
 
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.reset;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.verifyZeroInteractions;
 import static org.mockito.Mockito.when;
@@ -144,23 +145,32 @@
 
     @Test
     public void testDisableNotificationShade() {
-        when(mCentralSurfaces.getDisabled1()).thenReturn(StatusBarManager.DISABLE_NONE);
-        when(mCentralSurfaces.getDisabled2()).thenReturn(StatusBarManager.DISABLE_NONE);
+        // Start with nothing disabled
+        mSbcqCallbacks.disable(DEFAULT_DISPLAY, StatusBarManager.DISABLE_NONE,
+                StatusBarManager.DISABLE2_NONE, false);
+
         when(mCommandQueue.panelsEnabled()).thenReturn(false);
+        // WHEN the new disable flags have the shade disabled
         mSbcqCallbacks.disable(DEFAULT_DISPLAY, StatusBarManager.DISABLE_NONE,
                 StatusBarManager.DISABLE2_NOTIFICATION_SHADE, false);
 
+        // THEN the shade is collapsed
         verify(mShadeController).animateCollapseShade();
     }
 
     @Test
     public void testEnableNotificationShade() {
-        when(mCentralSurfaces.getDisabled1()).thenReturn(StatusBarManager.DISABLE_NONE);
-        when(mCentralSurfaces.getDisabled2())
-                .thenReturn(StatusBarManager.DISABLE2_NOTIFICATION_SHADE);
+        // Start with the shade disabled
+        mSbcqCallbacks.disable(DEFAULT_DISPLAY, StatusBarManager.DISABLE_NONE,
+                StatusBarManager.DISABLE2_NOTIFICATION_SHADE, false);
+        reset(mShadeController);
+
         when(mCommandQueue.panelsEnabled()).thenReturn(true);
+        // WHEN the new disable flags have the shade enabled
         mSbcqCallbacks.disable(DEFAULT_DISPLAY, StatusBarManager.DISABLE_NONE,
                 StatusBarManager.DISABLE2_NONE, false);
+
+        // THEN the shade is not collapsed
         verify(mShadeController, never()).animateCollapseShade();
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LockscreenWallpaperTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LockscreenWallpaperTest.kt
new file mode 100644
index 0000000..47671fb
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LockscreenWallpaperTest.kt
@@ -0,0 +1,101 @@
+/*
+ * 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
+
+import android.app.WallpaperManager
+import android.content.pm.UserInfo
+import android.os.Looper
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.user.data.model.SelectionStatus
+import com.android.systemui.user.data.repository.FakeUserRepository
+import com.android.systemui.util.kotlin.JavaAdapter
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.whenever
+import com.android.systemui.utils.os.FakeHandler
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.StandardTestDispatcher
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.mockito.ArgumentMatchers.eq
+import org.mockito.Mockito.verify
+
+@SmallTest
+@OptIn(ExperimentalCoroutinesApi::class)
+class LockscreenWallpaperTest : SysuiTestCase() {
+
+    private lateinit var underTest: LockscreenWallpaper
+
+    private val testScope = TestScope(StandardTestDispatcher())
+    private val userRepository = FakeUserRepository()
+
+    private val wallpaperManager: WallpaperManager = mock()
+
+    @Before
+    fun setUp() {
+        whenever(wallpaperManager.isLockscreenLiveWallpaperEnabled).thenReturn(false)
+        whenever(wallpaperManager.isWallpaperSupported).thenReturn(true)
+        underTest =
+            LockscreenWallpaper(
+                /* wallpaperManager= */ wallpaperManager,
+                /* iWallpaperManager= */ mock(),
+                /* keyguardUpdateMonitor= */ mock(),
+                /* dumpManager= */ mock(),
+                /* mediaManager= */ mock(),
+                /* mainHandler= */ FakeHandler(Looper.getMainLooper()),
+                /* javaAdapter= */ JavaAdapter(testScope.backgroundScope),
+                /* userRepository= */ userRepository,
+                /* userTracker= */ mock(),
+            )
+        underTest.start()
+    }
+
+    @Test
+    fun getBitmap_matchesUserIdFromUserRepo() =
+        testScope.runTest {
+            val info = UserInfo(/* id= */ 5, /* name= */ "id5", /* flags= */ 0)
+            userRepository.setUserInfos(listOf(info))
+            userRepository.setSelectedUserInfo(info)
+
+            underTest.bitmap
+
+            verify(wallpaperManager).getWallpaperFile(any(), eq(5))
+        }
+
+    @Test
+    fun getBitmap_usesOldUserIfNewUserInProgress() =
+        testScope.runTest {
+            val info5 = UserInfo(/* id= */ 5, /* name= */ "id5", /* flags= */ 0)
+            val info6 = UserInfo(/* id= */ 6, /* name= */ "id6", /* flags= */ 0)
+            userRepository.setUserInfos(listOf(info5, info6))
+            userRepository.setSelectedUserInfo(info5)
+
+            // WHEN the selection of user 6 is only in progress
+            userRepository.setSelectedUserInfo(
+                info6,
+                selectionStatus = SelectionStatus.SELECTION_IN_PROGRESS
+            )
+
+            underTest.bitmap
+
+            // THEN we still use user 5 for wallpaper selection
+            verify(wallpaperManager).getWallpaperFile(any(), eq(5))
+        }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt
index 74bcdee..862eb00 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt
@@ -73,7 +73,6 @@
 import kotlinx.coroutines.test.TestScope
 import kotlinx.coroutines.test.runCurrent
 import kotlinx.coroutines.test.runTest
-import org.junit.Assert.assertThrows
 import org.junit.Assert.assertTrue
 import org.junit.Before
 import org.junit.Test
@@ -588,11 +587,10 @@
         }
 
     @Test
-    fun testConnectionRepository_invalidSubId_throws() =
+    fun testConnectionRepository_invalidSubId_doesNotThrow() =
         testScope.runTest {
-            assertThrows(IllegalArgumentException::class.java) {
-                underTest.getRepoForSubId(SUB_1_ID)
-            }
+            underTest.getRepoForSubId(SUB_1_ID)
+            // No exception
         }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/CollapsedStatusBarViewModelImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/CollapsedStatusBarViewModelImplTest.kt
index 6301fa0..842d548 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/CollapsedStatusBarViewModelImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/CollapsedStatusBarViewModelImplTest.kt
@@ -20,7 +20,7 @@
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.coroutines.collectValues
 import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
-import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractorFactory
 import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.keyguard.shared.model.TransitionState
 import com.android.systemui.keyguard.shared.model.TransitionStep
@@ -48,7 +48,11 @@
 
         keyguardTransitionRepository = FakeKeyguardTransitionRepository()
         val interactor =
-            KeyguardTransitionInteractor(keyguardTransitionRepository, testScope.backgroundScope)
+            KeyguardTransitionInteractorFactory.create(
+                    scope = TestScope().backgroundScope,
+                    repository = keyguardTransitionRepository,
+                )
+                .keyguardTransitionInteractor
         underTest = CollapsedStatusBarViewModelImpl(interactor, testScope.backgroundScope)
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/usb/UsbPermissionActivityTest.kt b/packages/SystemUI/tests/src/com/android/systemui/usb/UsbPermissionActivityTest.kt
index b30c20d..b04eb01 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/usb/UsbPermissionActivityTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/usb/UsbPermissionActivityTest.kt
@@ -25,8 +25,8 @@
 import android.view.WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS
 import androidx.test.filters.SmallTest
 import androidx.test.rule.ActivityTestRule
-import androidx.test.runner.intercepting.SingleActivityFactory
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.activity.SingleActivityFactory
 import com.google.common.truth.Truth.assertThat
 
 import javax.inject.Inject
@@ -49,19 +49,17 @@
 
     open class UsbPermissionActivityTestable @Inject constructor (
         val message: UsbAudioWarningDialogMessage
-    )
-        : UsbPermissionActivity(UsbAudioWarningDialogMessage())
+    ) : UsbPermissionActivity(UsbAudioWarningDialogMessage())
 
     @Rule
     @JvmField
-    var activityRule = ActivityTestRule<UsbPermissionActivityTestable>(
-            object : SingleActivityFactory<UsbPermissionActivityTestable>(
-                    UsbPermissionActivityTestable::class.java
-            ) {
-                    override fun create(intent: Intent?): UsbPermissionActivityTestable {
-                        return UsbPermissionActivityTestable(mMessage)
-                    }
-            }, false, false)
+    var activityRule = ActivityTestRule(
+            /* activityFactory= */ SingleActivityFactory {
+                UsbPermissionActivityTestable(mMessage)
+            },
+            /* initialTouchMode= */ false,
+            /* launchActivity= */ false,
+    )
 
     private val activityIntent = Intent(mContext, UsbPermissionActivityTestable::class.java)
             .apply {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/user/data/repository/UserRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/user/data/repository/UserRepositoryImplTest.kt
index 079fbcd..0c28cbb 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/user/data/repository/UserRepositoryImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/user/data/repository/UserRepositoryImplTest.kt
@@ -26,6 +26,8 @@
 import com.android.systemui.flags.FakeFeatureFlags
 import com.android.systemui.flags.Flags.FACE_AUTH_REFACTOR
 import com.android.systemui.settings.FakeUserTracker
+import com.android.systemui.user.data.model.SelectedUserModel
+import com.android.systemui.user.data.model.SelectionStatus
 import com.android.systemui.user.data.model.UserSwitcherSettingsModel
 import com.android.systemui.util.settings.FakeSettings
 import com.google.common.truth.Truth.assertThat
@@ -224,6 +226,40 @@
     }
 
     @Test
+    fun userTrackerCallback_updatesSelectionStatus() = runSelfCancelingTest {
+        underTest = create(this)
+        var selectedUser: SelectedUserModel? = null
+        underTest.selectedUser.onEach { selectedUser = it }.launchIn(this)
+        setUpUsers(count = 2, selectedIndex = 1)
+
+        // WHEN the user is changing
+        tracker.onUserChanging(userId = 1)
+
+        // THEN the selection status is IN_PROGRESS
+        assertThat(selectedUser!!.selectionStatus).isEqualTo(SelectionStatus.SELECTION_IN_PROGRESS)
+
+        // WHEN the user has finished changing
+        tracker.onUserChanged(userId = 1)
+
+        // THEN the selection status is COMPLETE
+        assertThat(selectedUser!!.selectionStatus).isEqualTo(SelectionStatus.SELECTION_COMPLETE)
+
+        tracker.onProfileChanged()
+        assertThat(selectedUser!!.selectionStatus).isEqualTo(SelectionStatus.SELECTION_COMPLETE)
+
+        setUpUsers(count = 2, selectedIndex = 0)
+
+        tracker.onUserChanging(userId = 0)
+        assertThat(selectedUser!!.selectionStatus).isEqualTo(SelectionStatus.SELECTION_IN_PROGRESS)
+
+        // WHEN a profile change occurs while a user is changing
+        tracker.onProfileChanged()
+
+        // THEN the selection status remains as IN_PROGRESS
+        assertThat(selectedUser!!.selectionStatus).isEqualTo(SelectionStatus.SELECTION_IN_PROGRESS)
+    }
+
+    @Test
     fun userSwitchingInProgress_registersUserTrackerCallback() = runSelfCancelingTest {
         underTest = create(this)
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
index a09af00..ef12b2a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
@@ -39,6 +39,7 @@
 import static org.mockito.ArgumentMatchers.anyString;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.atLeastOnce;
+import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.times;
@@ -153,6 +154,7 @@
 import com.android.wm.shell.sysui.ShellController;
 import com.android.wm.shell.sysui.ShellInit;
 import com.android.wm.shell.taskview.TaskViewTransitions;
+import com.android.wm.shell.transition.Transitions;
 
 import org.junit.After;
 import org.junit.Before;
@@ -282,7 +284,7 @@
     @Mock
     private ScreenOffAnimationController mScreenOffAnimationController;
     @Mock
-    private TaskViewTransitions mTaskViewTransitions;
+    Transitions mTransitions;
     @Mock
     private Optional<OneHandedController> mOneHandedOptional;
     @Mock
@@ -294,6 +296,8 @@
     @Mock
     private Icon mAppBubbleIcon;
 
+    private TaskViewTransitions mTaskViewTransitions;
+
     private TestableBubblePositioner mPositioner;
 
     private BubbleData mBubbleData;
@@ -309,6 +313,12 @@
     @Before
     public void setUp() throws Exception {
         MockitoAnnotations.initMocks(this);
+
+        if (Transitions.ENABLE_SHELL_TRANSITIONS) {
+            doReturn(true).when(mTransitions).isRegistered();
+        }
+        mTaskViewTransitions = new TaskViewTransitions(mTransitions);
+
         mTestableLooper = TestableLooper.get(this);
 
         // For the purposes of this test, just run everything synchronously
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/activity/SingleActivityFactory.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/activity/SingleActivityFactory.kt
new file mode 100644
index 0000000..5a92fb1
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/activity/SingleActivityFactory.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.activity
+
+import android.app.Activity
+import android.content.Intent
+import androidx.test.runner.intercepting.SingleActivityFactory
+
+/**
+ * Builds a new [SingleActivityFactory] which delegating any call of [SingleActivityFactory.create]
+ * to the [instantiate] parameter.
+ *
+ * For more details, see [SingleActivityFactory].
+ */
+inline fun <reified T : Activity> SingleActivityFactory(
+    crossinline instantiate: (intent: Intent?) -> T,
+): SingleActivityFactory<T> {
+    return object : SingleActivityFactory<T>(T::class.java) {
+        override fun create(intent: Intent?): T = instantiate(intent)
+    }
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorFactory.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorFactory.kt
new file mode 100644
index 0000000..312ade5
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorFactory.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.keyguard.domain.interactor
+
+import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
+import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
+import kotlinx.coroutines.CoroutineScope
+
+/**
+ * Helper to create a new KeyguardTransitionInteractor in a way that doesn't require modifying 20+
+ * tests whenever we add a constructor param.
+ */
+object KeyguardTransitionInteractorFactory {
+    @JvmOverloads
+    @JvmStatic
+    fun create(
+        scope: CoroutineScope,
+        repository: KeyguardTransitionRepository = FakeKeyguardTransitionRepository(),
+    ): WithDependencies {
+        return WithDependencies(
+            repository = repository,
+            KeyguardTransitionInteractor(
+                scope = scope,
+                repository = repository,
+            )
+        )
+    }
+
+    data class WithDependencies(
+        val repository: KeyguardTransitionRepository,
+        val keyguardTransitionInteractor: KeyguardTransitionInteractor,
+    )
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/user/data/repository/FakeUserRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/user/data/repository/FakeUserRepository.kt
index 61e5b5f..51ee0c0 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/user/data/repository/FakeUserRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/user/data/repository/FakeUserRepository.kt
@@ -19,18 +19,28 @@
 
 import android.content.pm.UserInfo
 import android.os.UserHandle
+import com.android.systemui.user.data.model.SelectedUserModel
+import com.android.systemui.user.data.model.SelectionStatus
 import com.android.systemui.user.data.model.UserSwitcherSettingsModel
 import java.util.concurrent.atomic.AtomicBoolean
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.asStateFlow
-import kotlinx.coroutines.flow.filterNotNull
+import kotlinx.coroutines.flow.map
 import kotlinx.coroutines.yield
 
 class FakeUserRepository : UserRepository {
     companion object {
         // User id to represent a non system (human) user id. We presume this is the main user.
         private const val MAIN_USER_ID = 10
+
+        private val DEFAULT_SELECTED_USER = 0
+        private val DEFAULT_SELECTED_USER_INFO =
+            UserInfo(
+                /* id= */ DEFAULT_SELECTED_USER,
+                /* name= */ "default selected user",
+                /* flags= */ 0,
+            )
     }
 
     private val _userSwitcherSettings = MutableStateFlow(UserSwitcherSettingsModel())
@@ -40,8 +50,11 @@
     private val _userInfos = MutableStateFlow<List<UserInfo>>(emptyList())
     override val userInfos: Flow<List<UserInfo>> = _userInfos.asStateFlow()
 
-    private val _selectedUserInfo = MutableStateFlow<UserInfo?>(null)
-    override val selectedUserInfo: Flow<UserInfo> = _selectedUserInfo.filterNotNull()
+    override val selectedUser =
+        MutableStateFlow(
+            SelectedUserModel(DEFAULT_SELECTED_USER_INFO, SelectionStatus.SELECTION_COMPLETE)
+        )
+    override val selectedUserInfo: Flow<UserInfo> = selectedUser.map { it.userInfo }
 
     private val _userSwitchingInProgress = MutableStateFlow(false)
     override val userSwitchingInProgress: Flow<Boolean>
@@ -72,7 +85,7 @@
     }
 
     override fun getSelectedUserInfo(): UserInfo {
-        return checkNotNull(_selectedUserInfo.value)
+        return selectedUser.value.userInfo
     }
 
     override fun isSimpleUserSwitcher(): Boolean {
@@ -87,12 +100,15 @@
         _userInfos.value = infos
     }
 
-    suspend fun setSelectedUserInfo(userInfo: UserInfo) {
+    suspend fun setSelectedUserInfo(
+        userInfo: UserInfo,
+        selectionStatus: SelectionStatus = SelectionStatus.SELECTION_COMPLETE,
+    ) {
         check(_userInfos.value.contains(userInfo)) {
             "Cannot select the following user, it is not in the list of user infos: $userInfo!"
         }
 
-        _selectedUserInfo.value = userInfo
+        selectedUser.value = SelectedUserModel(userInfo, selectionStatus)
         yield()
     }
 
diff --git a/packages/services/CameraExtensionsProxy/src/com/android/cameraextensions/CameraExtensionsProxyService.java b/packages/services/CameraExtensionsProxy/src/com/android/cameraextensions/CameraExtensionsProxyService.java
index c2ebddf..502ee4d 100644
--- a/packages/services/CameraExtensionsProxy/src/com/android/cameraextensions/CameraExtensionsProxyService.java
+++ b/packages/services/CameraExtensionsProxy/src/com/android/cameraextensions/CameraExtensionsProxyService.java
@@ -75,6 +75,7 @@
 import android.util.Size;
 import android.view.Surface;
 
+import androidx.annotation.NonNull;
 import androidx.camera.extensions.impl.AutoImageCaptureExtenderImpl;
 import androidx.camera.extensions.impl.AutoPreviewExtenderImpl;
 import androidx.camera.extensions.impl.BeautyImageCaptureExtenderImpl;
@@ -204,7 +205,7 @@
      * A per-process global camera extension manager instance, to track and
      * initialize/release extensions depending on client activity.
      */
-    private static final class CameraExtensionManagerGlobal {
+    private static final class CameraExtensionManagerGlobal implements IBinder.DeathRecipient {
         private static final String TAG = "CameraExtensionManagerGlobal";
         private final int EXTENSION_DELAY_MS = 1000;
 
@@ -212,8 +213,9 @@
         private final HandlerThread mHandlerThread;
         private final Object mLock = new Object();
 
-        private long mCurrentClientCount = 0;
-        private ArraySet<Long> mActiveClients = new ArraySet<>();
+        private ArraySet<IBinder> mActiveClients = new ArraySet<>();
+        private HashMap<IBinder, ArraySet<IBinder.DeathRecipient>> mClientDeathRecipient =
+                new HashMap<>();
         private IInitializeSessionCallback mInitializeCb = null;
 
         // Singleton instance
@@ -314,8 +316,20 @@
             return GLOBAL_CAMERA_MANAGER;
         }
 
-        public long registerClient(Context ctx) {
+        public boolean registerClient(Context ctx, IBinder token) {
             synchronized (mLock) {
+                if (mActiveClients.contains(token)) {
+                    Log.e(TAG, "Failed to register existing client!");
+                    return false;
+                }
+
+                try {
+                    token.linkToDeath(this, 0);
+                } catch (RemoteException e) {
+                    Log.e(TAG, "Failed to link to binder token!");
+                    return false;
+                }
+
                 if (INIT_API_SUPPORTED) {
                     if (mActiveClients.isEmpty()) {
                         InitializerFuture status = new InitializerFuture();
@@ -327,43 +341,76 @@
                                     TimeUnit.MILLISECONDS);
                         } catch (TimeoutException e) {
                             Log.e(TAG, "Timed out while initializing camera extensions!");
-                            return -1;
+                            return false;
                         }
                         if (!initSuccess) {
                             Log.e(TAG, "Failed while initializing camera extensions!");
-                            return -1;
+                            return false;
                         }
                     }
                 }
 
-                long ret = mCurrentClientCount;
-                mCurrentClientCount++;
-                if (mCurrentClientCount < 0) {
-                    mCurrentClientCount = 0;
-                }
-                mActiveClients.add(ret);
+                mActiveClients.add(token);
+                mClientDeathRecipient.put(token, new ArraySet<>());
 
-                return ret;
+                return true;
             }
         }
 
-        public void unregisterClient(long clientId) {
+        public void unregisterClient(IBinder token) {
             synchronized (mLock) {
-                if (mActiveClients.remove(clientId) && mActiveClients.isEmpty() &&
-                        INIT_API_SUPPORTED) {
-                    InitializerFuture status = new InitializerFuture();
-                    InitializerImpl.deinit(new ReleaseHandler(status),
-                            new HandlerExecutor(mHandler));
-                    boolean releaseSuccess;
-                    try {
-                        releaseSuccess = status.get(EXTENSION_DELAY_MS, TimeUnit.MILLISECONDS);
-                    } catch (TimeoutException e) {
-                        Log.e(TAG, "Timed out while releasing camera extensions!");
-                        return;
+                if (mActiveClients.remove(token)) {
+                    token.unlinkToDeath(this, 0);
+                    mClientDeathRecipient.remove(token);
+                    if (mActiveClients.isEmpty() && INIT_API_SUPPORTED) {
+                        InitializerFuture status = new InitializerFuture();
+                        InitializerImpl.deinit(new ReleaseHandler(status),
+                                new HandlerExecutor(mHandler));
+                        boolean releaseSuccess;
+                        try {
+                            releaseSuccess = status.get(EXTENSION_DELAY_MS, TimeUnit.MILLISECONDS);
+                        } catch (TimeoutException e) {
+                            Log.e(TAG, "Timed out while releasing camera extensions!");
+                            return;
+                        }
+                        if (!releaseSuccess) {
+                            Log.e(TAG, "Failed while releasing camera extensions!");
+                        }
                     }
-                    if (!releaseSuccess) {
-                        Log.e(TAG, "Failed while releasing camera extensions!");
-                    }
+                }
+            }
+        }
+
+        @Override
+        public void binderDied() {
+            // Do nothing, handled below
+        }
+
+        @Override
+        public void binderDied(@NonNull IBinder who) {
+            synchronized (mLock) {
+                if (mClientDeathRecipient.containsKey(who)) {
+                    mClientDeathRecipient.get(who).stream().forEach(
+                            recipient -> recipient.binderDied(who));
+                }
+                unregisterClient(who);
+            }
+        }
+
+        public void registerDeathRecipient(IBinder token, IBinder.DeathRecipient recipient) {
+            synchronized (mLock) {
+                if (mClientDeathRecipient.containsKey(token)) {
+                    ArraySet<IBinder.DeathRecipient> recipients = mClientDeathRecipient.get(token);
+                    recipients.add(recipient);
+                }
+            }
+        }
+
+        public void unregisterDeathRecipient(IBinder token, IBinder.DeathRecipient recipient) {
+            synchronized (mLock) {
+                if (mClientDeathRecipient.containsKey(token)) {
+                    ArraySet<IBinder.DeathRecipient> recipients = mClientDeathRecipient.get(token);
+                    recipients.remove(recipient);
                 }
             }
         }
@@ -406,21 +453,35 @@
     /**
      * @hide
      */
-    private static long registerClient(Context ctx) {
+    private static boolean registerClient(Context ctx, IBinder token) {
         if (!EXTENSIONS_PRESENT) {
-            return -1;
+            return false;
         }
-        return CameraExtensionManagerGlobal.get().registerClient(ctx);
+        return CameraExtensionManagerGlobal.get().registerClient(ctx, token);
     }
 
     /**
      * @hide
      */
-    public static void unregisterClient(long clientId) {
+    public static void unregisterClient(IBinder token) {
         if (!EXTENSIONS_PRESENT) {
             return;
         }
-        CameraExtensionManagerGlobal.get().unregisterClient(clientId);
+        CameraExtensionManagerGlobal.get().unregisterClient(token);
+    }
+
+    /**
+     * @hide
+     */
+    private static void registerDeathRecipient(IBinder token, IBinder.DeathRecipient recipient) {
+        CameraExtensionManagerGlobal.get().registerDeathRecipient(token, recipient);
+    }
+
+    /**
+     * @hide
+     */
+    private static void unregisterDeathRecipient(IBinder token, IBinder.DeathRecipient recipient) {
+        CameraExtensionManagerGlobal.get().unregisterDeathRecipient(token, recipient);
     }
 
     /**
@@ -649,13 +710,14 @@
 
     private class CameraExtensionsProxyServiceStub extends ICameraExtensionsProxyService.Stub {
         @Override
-        public long registerClient() {
-            return CameraExtensionsProxyService.registerClient(CameraExtensionsProxyService.this);
+        public boolean registerClient(IBinder token) {
+            return CameraExtensionsProxyService.registerClient(CameraExtensionsProxyService.this,
+                    token);
         }
 
         @Override
-        public void unregisterClient(long clientId) {
-            CameraExtensionsProxyService.unregisterClient(clientId);
+        public void unregisterClient(IBinder token) {
+            CameraExtensionsProxyService.unregisterClient(token);
         }
 
         private boolean checkCameraPermission() {
@@ -1192,16 +1254,18 @@
         }
     }
 
-    private class SessionProcessorImplStub extends ISessionProcessorImpl.Stub {
+    private class SessionProcessorImplStub extends ISessionProcessorImpl.Stub implements
+            IBinder.DeathRecipient {
         private final SessionProcessorImpl mSessionProcessor;
         private String mCameraId = null;
+        private IBinder mToken;
 
         public SessionProcessorImplStub(SessionProcessorImpl sessionProcessor) {
             mSessionProcessor = sessionProcessor;
         }
 
         @Override
-        public CameraSessionConfig initSession(String cameraId,
+        public CameraSessionConfig initSession(IBinder token, String cameraId,
                 Map<String, CameraMetadataNative> charsMapNative, OutputSurface previewSurface,
                 OutputSurface imageCaptureSurface, OutputSurface postviewSurface) {
             OutputSurfaceImplStub outputPreviewSurfaceImpl =
@@ -1253,12 +1317,14 @@
             ret.sessionParameter = initializeParcelableMetadata(
                     sessionConfig.getSessionParameters(), cameraId);
             mCameraId = cameraId;
-
+            mToken = token;
+            CameraExtensionsProxyService.registerDeathRecipient(mToken, this);
             return ret;
         }
 
         @Override
-        public void deInitSession() {
+        public void deInitSession(IBinder token) {
+            CameraExtensionsProxyService.unregisterDeathRecipient(mToken, this);
             mSessionProcessor.deInitSession();
         }
 
@@ -1330,6 +1396,11 @@
 
             return null;
         }
+
+        @Override
+        public void binderDied() {
+            mSessionProcessor.deInitSession();
+        }
     }
 
     private class OutputSurfaceConfigurationImplStub implements OutputSurfaceConfigurationImpl {
@@ -1395,24 +1466,31 @@
         }
     }
 
-    private class PreviewExtenderImplStub extends IPreviewExtenderImpl.Stub {
+    private class PreviewExtenderImplStub extends IPreviewExtenderImpl.Stub implements
+            IBinder.DeathRecipient {
         private final PreviewExtenderImpl mPreviewExtender;
         private String mCameraId = null;
+        private boolean mSessionEnabled;
+        private IBinder mToken;
 
         public PreviewExtenderImplStub(PreviewExtenderImpl previewExtender) {
             mPreviewExtender = previewExtender;
         }
 
         @Override
-        public void onInit(String cameraId, CameraMetadataNative cameraCharacteristics) {
+        public void onInit(IBinder token, String cameraId,
+                CameraMetadataNative cameraCharacteristics) {
             mCameraId = cameraId;
             CameraCharacteristics chars = new CameraCharacteristics(cameraCharacteristics);
             mCameraManager.registerDeviceStateListener(chars);
             mPreviewExtender.onInit(cameraId, chars, CameraExtensionsProxyService.this);
+            mToken = token;
+            CameraExtensionsProxyService.registerDeathRecipient(mToken, this);
         }
 
         @Override
-        public void onDeInit() {
+        public void onDeInit(IBinder token) {
+            CameraExtensionsProxyService.unregisterDeathRecipient(mToken, this);
             mPreviewExtender.onDeInit();
         }
 
@@ -1423,11 +1501,13 @@
 
         @Override
         public CaptureStageImpl onEnableSession() {
+            mSessionEnabled = true;
             return initializeParcelable(mPreviewExtender.onEnableSession(), mCameraId);
         }
 
         @Override
         public CaptureStageImpl onDisableSession() {
+            mSessionEnabled = false;
             return initializeParcelable(mPreviewExtender.onDisableSession(), mCameraId);
         }
 
@@ -1516,26 +1596,41 @@
             }
             return null;
         }
+
+        @Override
+        public void binderDied() {
+            if (mSessionEnabled) {
+                mPreviewExtender.onDisableSession();
+            }
+            mPreviewExtender.onDeInit();
+        }
     }
 
-    private class ImageCaptureExtenderImplStub extends IImageCaptureExtenderImpl.Stub {
+    private class ImageCaptureExtenderImplStub extends IImageCaptureExtenderImpl.Stub implements
+            IBinder.DeathRecipient {
         private final ImageCaptureExtenderImpl mImageExtender;
         private String mCameraId = null;
+        private boolean mSessionEnabled;
+        private IBinder mToken;
 
         public ImageCaptureExtenderImplStub(ImageCaptureExtenderImpl imageExtender) {
             mImageExtender = imageExtender;
         }
 
         @Override
-        public void onInit(String cameraId, CameraMetadataNative cameraCharacteristics) {
+        public void onInit(IBinder token, String cameraId,
+                CameraMetadataNative cameraCharacteristics) {
             CameraCharacteristics chars = new CameraCharacteristics(cameraCharacteristics);
             mCameraManager.registerDeviceStateListener(chars);
             mImageExtender.onInit(cameraId, chars, CameraExtensionsProxyService.this);
             mCameraId = cameraId;
+            mToken = token;
+            CameraExtensionsProxyService.registerDeathRecipient(mToken, this);
         }
 
         @Override
-        public void onDeInit() {
+        public void onDeInit(IBinder token) {
+            CameraExtensionsProxyService.unregisterDeathRecipient(mToken, this);
             mImageExtender.onDeInit();
         }
 
@@ -1564,11 +1659,13 @@
 
         @Override
         public CaptureStageImpl onEnableSession() {
+            mSessionEnabled = true;
             return initializeParcelable(mImageExtender.onEnableSession(), mCameraId);
         }
 
         @Override
         public CaptureStageImpl onDisableSession() {
+            mSessionEnabled = false;
             return initializeParcelable(mImageExtender.onDisableSession(), mCameraId);
         }
 
@@ -1737,6 +1834,14 @@
 
             return null;
         }
+
+        @Override
+        public void binderDied() {
+            if (mSessionEnabled) {
+                mImageExtender.onDisableSession();
+            }
+            mImageExtender.onDeInit();
+        }
     }
 
     private class ProcessResultCallback implements ProcessResultImpl {
diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationController.java b/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationController.java
index b1cdc50..ba3d434 100644
--- a/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationController.java
+++ b/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationController.java
@@ -319,6 +319,10 @@
                     FullScreenMagnificationController::onUserContextChanged,
                     FullScreenMagnificationController.this, mDisplayId);
             mControllerCtx.getHandler().sendMessage(m);
+
+            synchronized (mLock) {
+                refreshThumbnail();
+            }
         }
 
         @Override
@@ -344,7 +348,7 @@
                     mMagnificationRegion.set(magnified);
                     mMagnificationRegion.getBounds(mMagnificationBounds);
 
-                    refreshThumbnail(getScale(), getCenterX(), getCenterY());
+                    refreshThumbnail();
 
                     // It's possible that our magnification spec is invalid with the new bounds.
                     // Adjust the current spec's offsets if necessary.
@@ -602,13 +606,13 @@
         }
 
         @GuardedBy("mLock")
-        void refreshThumbnail(float scale, float centerX, float centerY) {
+        void refreshThumbnail() {
             if (mMagnificationThumbnail != null) {
                 mMagnificationThumbnail.setThumbnailBounds(
                         mMagnificationBounds,
-                        scale,
-                        centerX,
-                        centerY
+                        getScale(),
+                        getCenterX(),
+                        getCenterY()
                 );
             }
         }
@@ -627,7 +631,7 @@
                 // We call refreshThumbnail when the thumbnail is just created to set current
                 // magnification bounds to thumbnail. It to prevent the thumbnail size has not yet
                 // updated properly and thus shows with huge size. (b/276314641)
-                refreshThumbnail(getScale(), getCenterX(), getCenterY());
+                refreshThumbnail();
             }
         }
 
diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationController.java b/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationController.java
index 87fbee7..f6948e9 100644
--- a/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationController.java
+++ b/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationController.java
@@ -502,6 +502,10 @@
     @Override
     public void onSourceBoundsChanged(int displayId, Rect bounds) {
         if (shouldNotifyMagnificationChange(displayId, MAGNIFICATION_MODE_WINDOW)) {
+            // notify sysui the magnification scale changed on window magnifier
+            mWindowMagnificationMgr.onUserMagnificationScaleChanged(
+                    mUserId, displayId, getWindowMagnificationMgr().getScale(displayId));
+
             final MagnificationConfig config = new MagnificationConfig.Builder()
                     .setMode(MAGNIFICATION_MODE_WINDOW)
                     .setActivated(getWindowMagnificationMgr().isWindowMagnifierEnabled(displayId))
@@ -516,6 +520,10 @@
     public void onFullScreenMagnificationChanged(int displayId, @NonNull Region region,
             @NonNull MagnificationConfig config) {
         if (shouldNotifyMagnificationChange(displayId, MAGNIFICATION_MODE_FULLSCREEN)) {
+            // notify sysui the magnification scale changed on fullscreen magnifier
+            mWindowMagnificationMgr.onUserMagnificationScaleChanged(
+                    mUserId, displayId, config.getScale());
+
             mAms.notifyMagnificationChanged(displayId, region, config);
         }
     }
diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationThumbnail.java b/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationThumbnail.java
index 03fa93d..a7bdd5a 100644
--- a/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationThumbnail.java
+++ b/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationThumbnail.java
@@ -99,15 +99,17 @@
             Log.d(LOG_TAG, "setThumbnailBounds " + currentBounds);
         }
         mHandler.post(() -> {
-            mWindowBounds = currentBounds;
-            setBackgroundBounds();
+            refreshBackgroundBounds(currentBounds);
             if (mVisible) {
                 updateThumbnailMainThread(scale, centerX, centerY);
             }
         });
     }
 
-    private void setBackgroundBounds() {
+    @MainThread
+    private void refreshBackgroundBounds(Rect currentBounds) {
+        mWindowBounds = currentBounds;
+
         Point magnificationBoundary = getMagnificationThumbnailPadding(mContext);
         mThumbnailWidth = (int) (mWindowBounds.width() / BG_ASPECT_RATIO);
         mThumbnailHeight = (int) (mWindowBounds.height() / BG_ASPECT_RATIO);
@@ -117,6 +119,10 @@
         mBackgroundParams.height = mThumbnailHeight;
         mBackgroundParams.x = initX;
         mBackgroundParams.y = initY;
+
+        if (mVisible) {
+            mWindowManager.updateViewLayout(mThumbnailLayout, mBackgroundParams);
+        }
     }
 
     @MainThread
@@ -264,21 +270,16 @@
             mThumbnailView.setScaleX(scaleDown);
             mThumbnailView.setScaleY(scaleDown);
         }
-        float thumbnailWidth;
-        float thumbnailHeight;
-        if (mThumbnailView.getWidth() == 0 || mThumbnailView.getHeight() == 0) {
-            // if the thumbnail view size is not updated correctly, we just use the cached values.
-            thumbnailWidth = mThumbnailWidth;
-            thumbnailHeight = mThumbnailHeight;
-        } else {
-            thumbnailWidth = mThumbnailView.getWidth();
-            thumbnailHeight = mThumbnailView.getHeight();
-        }
-        if (!Float.isNaN(centerX)) {
+
+        if (!Float.isNaN(centerX)
+                && !Float.isNaN(centerY)
+                && mThumbnailWidth > 0
+                && mThumbnailHeight > 0
+        ) {
             var padding = mThumbnailView.getPaddingTop();
             var ratio = 1f / BG_ASPECT_RATIO;
-            var centerXScaled = centerX * ratio - (thumbnailWidth / 2f + padding);
-            var centerYScaled = centerY * ratio - (thumbnailHeight / 2f + padding);
+            var centerXScaled = centerX * ratio - (mThumbnailWidth / 2f + padding);
+            var centerYScaled = centerY * ratio - (mThumbnailHeight / 2f + padding);
 
             if (DEBUG) {
                 Log.d(
diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationConnectionWrapper.java b/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationConnectionWrapper.java
index 1202cfa..6c6394f 100644
--- a/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationConnectionWrapper.java
+++ b/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationConnectionWrapper.java
@@ -201,6 +201,22 @@
         return true;
     }
 
+    boolean onUserMagnificationScaleChanged(int userId, int displayId, float scale) {
+        if (mTrace.isA11yTracingEnabledForTypes(FLAGS_WINDOW_MAGNIFICATION_CONNECTION)) {
+            mTrace.logTrace(TAG + ".onMagnificationScaleUpdated",
+                    FLAGS_WINDOW_MAGNIFICATION_CONNECTION, "displayId=" + displayId);
+        }
+        try {
+            mConnection.onUserMagnificationScaleChanged(userId, displayId, scale);
+        } catch (RemoteException e) {
+            if (DBG) {
+                Slog.e(TAG, "Error calling onMagnificationScaleUpdated()", e);
+            }
+            return false;
+        }
+        return true;
+    }
+
     boolean setConnectionCallback(IWindowMagnificationConnectionCallback connectionCallback) {
         if (mTrace.isA11yTracingEnabledForTypes(
                 FLAGS_WINDOW_MAGNIFICATION_CONNECTION
diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationManager.java b/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationManager.java
index d07db3f..d96682c 100644
--- a/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationManager.java
+++ b/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationManager.java
@@ -827,6 +827,20 @@
     }
 
     /**
+     * Notify System UI the magnification scale on the specified display for userId is changed.
+     *
+     * @param userId the user id.
+     * @param displayId the logical display id.
+     * @param scale magnification scale.
+     */
+    public boolean onUserMagnificationScaleChanged(int userId, int displayId, float scale) {
+        synchronized (mLock) {
+            return mConnectionWrapper != null
+                    && mConnectionWrapper.onUserMagnificationScaleChanged(userId, displayId, scale);
+        }
+    }
+
+    /**
      * Returns the screen-relative X coordinate of the center of the magnified bounds.
      *
      * @param displayId The logical display id
diff --git a/services/core/java/com/android/server/am/PendingIntentController.java b/services/core/java/com/android/server/am/PendingIntentController.java
index 7ee96aa..a20623c 100644
--- a/services/core/java/com/android/server/am/PendingIntentController.java
+++ b/services/core/java/com/android/server/am/PendingIntentController.java
@@ -26,6 +26,7 @@
 import android.annotation.Nullable;
 import android.app.Activity;
 import android.app.ActivityManagerInternal;
+import android.app.ActivityOptions;
 import android.app.AppGlobals;
 import android.app.PendingIntent;
 import android.app.PendingIntentStats;
@@ -126,6 +127,18 @@
                 }
             }
             Bundle.setDefusable(bOptions, true);
+            ActivityOptions opts = ActivityOptions.fromBundle(bOptions);
+            if (opts != null && opts.getPendingIntentBackgroundActivityStartMode()
+                    != ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_SYSTEM_DEFINED) {
+                Slog.wtf(TAG, "Resetting option setPendingIntentBackgroundActivityStartMode("
+                        + opts.getPendingIntentBackgroundActivityStartMode()
+                        + ") to SYSTEM_DEFINED from the options provided by the pending "
+                        + "intent creator ("
+                        + packageName
+                        + ") because this option is meant for the pending intent sender");
+                opts.setPendingIntentBackgroundActivityStartMode(
+                        ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_SYSTEM_DEFINED);
+            }
 
             final boolean noCreate = (flags & PendingIntent.FLAG_NO_CREATE) != 0;
             final boolean cancelCurrent = (flags & PendingIntent.FLAG_CANCEL_CURRENT) != 0;
@@ -135,7 +148,7 @@
 
             PendingIntentRecord.Key key = new PendingIntentRecord.Key(type, packageName, featureId,
                     token, resultWho, requestCode, intents, resolvedTypes, flags,
-                    SafeActivityOptions.fromBundle(bOptions), userId);
+                    new SafeActivityOptions(opts), userId);
             WeakReference<PendingIntentRecord> ref;
             ref = mIntentSenderRecords.get(key);
             PendingIntentRecord rec = ref != null ? ref.get() : null;
diff --git a/services/core/java/com/android/server/am/PendingIntentRecord.java b/services/core/java/com/android/server/am/PendingIntentRecord.java
index 202d407..a0e76f1 100644
--- a/services/core/java/com/android/server/am/PendingIntentRecord.java
+++ b/services/core/java/com/android/server/am/PendingIntentRecord.java
@@ -457,6 +457,20 @@
             // can specify a consistent launch mode even if the PendingIntent is immutable
             final ActivityOptions opts = ActivityOptions.fromBundle(options);
             if (opts != null) {
+                if (opts.getPendingIntentCreatorBackgroundActivityStartMode()
+                        != ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_SYSTEM_DEFINED) {
+                    Slog.wtf(TAG,
+                            "Resetting option "
+                                    + "setPendingIntentCreatorBackgroundActivityStartMode("
+                                    + opts.getPendingIntentCreatorBackgroundActivityStartMode()
+                                    + ") to SYSTEM_DEFINED from the options provided by the "
+                                    + "pending intent sender ("
+                                    + key.packageName
+                                    + ") because this option is meant for the pending intent "
+                                    + "creator");
+                    opts.setPendingIntentCreatorBackgroundActivityStartMode(
+                            ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_SYSTEM_DEFINED);
+                }
                 finalIntent.addFlags(opts.getPendingIntentLaunchFlags());
             }
 
diff --git a/services/core/java/com/android/server/am/UserController.java b/services/core/java/com/android/server/am/UserController.java
index 76a994e..99c2f8a 100644
--- a/services/core/java/com/android/server/am/UserController.java
+++ b/services/core/java/com/android/server/am/UserController.java
@@ -1972,7 +1972,7 @@
     }
 
     private void dismissUserSwitchDialog(Runnable onDismissed) {
-        mInjector.dismissUserSwitchingDialog(onDismissed);
+        mUiHandler.post(() -> mInjector.dismissUserSwitchingDialog(onDismissed));
     }
 
     private void showUserSwitchDialog(Pair<UserInfo, UserInfo> fromToUserPair) {
diff --git a/services/core/java/com/android/server/audio/AudioDeviceBroker.java b/services/core/java/com/android/server/audio/AudioDeviceBroker.java
index 29a1941..393e430 100644
--- a/services/core/java/com/android/server/audio/AudioDeviceBroker.java
+++ b/services/core/java/com/android/server/audio/AudioDeviceBroker.java
@@ -30,6 +30,8 @@
 import android.media.AudioDeviceAttributes;
 import android.media.AudioDeviceInfo;
 import android.media.AudioManager;
+import android.media.AudioPlaybackConfiguration;
+import android.media.AudioRecordingConfiguration;
 import android.media.AudioRoutesInfo;
 import android.media.AudioSystem;
 import android.media.BluetoothProfileConnectionInfo;
@@ -289,37 +291,38 @@
      * @param on
      * @param eventSource for logging purposes
      */
-    /*package*/ void setSpeakerphoneOn(IBinder cb, int pid, boolean on, String eventSource) {
+    /*package*/ void setSpeakerphoneOn(
+            IBinder cb, int uid, boolean on, boolean isPrivileged, String eventSource) {
 
         if (AudioService.DEBUG_COMM_RTE) {
-            Log.v(TAG, "setSpeakerphoneOn, on: " + on + " pid: " + pid);
+            Log.v(TAG, "setSpeakerphoneOn, on: " + on + " uid: " + uid);
         }
         postSetCommunicationDeviceForClient(new CommunicationDeviceInfo(
-                cb, pid, new AudioDeviceAttributes(AudioSystem.DEVICE_OUT_SPEAKER, ""),
-                on, BtHelper.SCO_MODE_UNDEFINED, eventSource, false));
+                cb, uid, new AudioDeviceAttributes(AudioSystem.DEVICE_OUT_SPEAKER, ""),
+                on, BtHelper.SCO_MODE_UNDEFINED, eventSource, false, isPrivileged));
     }
 
     /**
      * Select device for use for communication use cases.
      * @param cb Client binder for death detection
-     * @param pid Client pid
+     * @param uid Client uid
      * @param device Device selected or null to unselect.
      * @param eventSource for logging purposes
      */
 
     private static final long SET_COMMUNICATION_DEVICE_TIMEOUT_MS = 3000;
 
-    /*package*/ boolean setCommunicationDevice(
-            IBinder cb, int pid, AudioDeviceInfo device, String eventSource) {
+    /*package*/ boolean setCommunicationDevice(IBinder cb, int uid, AudioDeviceInfo device,
+                                               boolean isPrivileged, String eventSource) {
 
         if (AudioService.DEBUG_COMM_RTE) {
-            Log.v(TAG, "setCommunicationDevice, device: " + device + ", pid: " + pid);
+            Log.v(TAG, "setCommunicationDevice, device: " + device + ", uid: " + uid);
         }
 
         AudioDeviceAttributes deviceAttr =
                 (device != null) ? new AudioDeviceAttributes(device) : null;
-        CommunicationDeviceInfo deviceInfo = new CommunicationDeviceInfo(cb, pid, deviceAttr,
-                device != null, BtHelper.SCO_MODE_UNDEFINED, eventSource, true);
+        CommunicationDeviceInfo deviceInfo = new CommunicationDeviceInfo(cb, uid, deviceAttr,
+                device != null, BtHelper.SCO_MODE_UNDEFINED, eventSource, true, isPrivileged);
         postSetCommunicationDeviceForClient(deviceInfo);
         boolean status;
         synchronized (deviceInfo) {
@@ -353,7 +356,7 @@
             Log.v(TAG, "onSetCommunicationDeviceForClient: " + deviceInfo);
         }
         if (!deviceInfo.mOn) {
-            CommunicationRouteClient client = getCommunicationRouteClientForPid(deviceInfo.mPid);
+            CommunicationRouteClient client = getCommunicationRouteClientForUid(deviceInfo.mUid);
             if (client == null || (deviceInfo.mDevice != null
                     && !deviceInfo.mDevice.equals(client.getDevice()))) {
                 return false;
@@ -361,22 +364,23 @@
         }
 
         AudioDeviceAttributes device = deviceInfo.mOn ? deviceInfo.mDevice : null;
-        setCommunicationRouteForClient(deviceInfo.mCb, deviceInfo.mPid, device,
-                deviceInfo.mScoAudioMode, deviceInfo.mEventSource);
+        setCommunicationRouteForClient(deviceInfo.mCb, deviceInfo.mUid, device,
+                deviceInfo.mScoAudioMode, deviceInfo.mIsPrivileged, deviceInfo.mEventSource);
         return true;
     }
 
     @GuardedBy("mDeviceStateLock")
     /*package*/ void setCommunicationRouteForClient(
-                            IBinder cb, int pid, AudioDeviceAttributes device,
-                            int scoAudioMode, String eventSource) {
+                            IBinder cb, int uid, AudioDeviceAttributes device,
+                            int scoAudioMode, boolean isPrivileged, String eventSource) {
 
         if (AudioService.DEBUG_COMM_RTE) {
-            Log.v(TAG, "setCommunicationRouteForClient: device: " + device);
+            Log.v(TAG, "setCommunicationRouteForClient: device: " + device
+                    + ", eventSource: " + eventSource);
         }
         AudioService.sDeviceLogger.enqueue((new EventLogger.StringEvent(
-                                        "setCommunicationRouteForClient for pid: " + pid
-                                        + " device: " + device
+                                        "setCommunicationRouteForClient for uid: " + uid
+                                        + " device: " + device + " isPrivileged: " + isPrivileged
                                         + " from API: " + eventSource)).printLog(TAG));
 
         final boolean wasBtScoRequested = isBluetoothScoRequested();
@@ -385,16 +389,18 @@
 
         // Save previous client route in case of failure to start BT SCO audio
         AudioDeviceAttributes prevClientDevice = null;
-        client = getCommunicationRouteClientForPid(pid);
+        boolean prevPrivileged = false;
+        client = getCommunicationRouteClientForUid(uid);
         if (client != null) {
             prevClientDevice = client.getDevice();
+            prevPrivileged = client.isPrivileged();
         }
 
         if (device != null) {
-            client = addCommunicationRouteClient(cb, pid, device);
+            client = addCommunicationRouteClient(cb, uid, device, isPrivileged);
             if (client == null) {
-                Log.w(TAG, "setCommunicationRouteForClient: could not add client for pid: "
-                        + pid + " and device: " + device);
+                Log.w(TAG, "setCommunicationRouteForClient: could not add client for uid: "
+                        + uid + " and device: " + device);
             }
         } else {
             client = removeCommunicationRouteClient(cb, true);
@@ -406,11 +412,11 @@
         boolean isBtScoRequested = isBluetoothScoRequested();
         if (isBtScoRequested && (!wasBtScoRequested || !isBluetoothScoActive())) {
             if (!mBtHelper.startBluetoothSco(scoAudioMode, eventSource)) {
-                Log.w(TAG, "setCommunicationRouteForClient: failure to start BT SCO for pid: "
-                        + pid);
+                Log.w(TAG, "setCommunicationRouteForClient: failure to start BT SCO for uid: "
+                        + uid);
                 // clean up or restore previous client selection
                 if (prevClientDevice != null) {
-                    addCommunicationRouteClient(cb, pid, prevClientDevice);
+                    addCommunicationRouteClient(cb, uid, prevClientDevice, prevPrivileged);
                 } else {
                     removeCommunicationRouteClient(cb, true);
                 }
@@ -447,11 +453,12 @@
     @GuardedBy("mDeviceStateLock")
     private CommunicationRouteClient topCommunicationRouteClient() {
         for (CommunicationRouteClient crc : mCommunicationRouteClients) {
-            if (crc.getPid() == mAudioModeOwner.mPid) {
+            if (crc.getUid() == mAudioModeOwner.mUid) {
                 return crc;
             }
         }
-        if (!mCommunicationRouteClients.isEmpty() && mAudioModeOwner.mPid == 0) {
+        if (!mCommunicationRouteClients.isEmpty() && mAudioModeOwner.mPid == 0
+                && mCommunicationRouteClients.get(0).isActive()) {
             return mCommunicationRouteClients.get(0);
         }
         return null;
@@ -491,14 +498,48 @@
     };
 
     /*package */ static boolean isValidCommunicationDevice(AudioDeviceInfo device) {
+        return isValidCommunicationDeviceType(device.getType());
+    }
+
+    private static boolean isValidCommunicationDeviceType(int deviceType) {
         for (int type : VALID_COMMUNICATION_DEVICE_TYPES) {
-            if (device.getType() == type) {
+            if (deviceType == type) {
                 return true;
             }
         }
         return false;
     }
 
+    /*package */
+    void postCheckCommunicationDeviceRemoval(@NonNull AudioDeviceAttributes device) {
+        if (!isValidCommunicationDeviceType(
+                AudioDeviceInfo.convertInternalDeviceToDeviceType(device.getInternalType()))) {
+            return;
+        }
+        sendLMsgNoDelay(MSG_L_CHECK_COMMUNICATION_DEVICE_REMOVAL, SENDMSG_QUEUE, device);
+    }
+
+    @GuardedBy("mDeviceStateLock")
+    void onCheckCommunicationDeviceRemoval(@NonNull AudioDeviceAttributes device) {
+        if (AudioService.DEBUG_COMM_RTE) {
+            Log.v(TAG, "onCheckCommunicationDeviceRemoval device: " + device.toString());
+        }
+        for (CommunicationRouteClient crc : mCommunicationRouteClients) {
+            if (device.equals(crc.getDevice())) {
+                if (AudioService.DEBUG_COMM_RTE) {
+                    Log.v(TAG, "onCheckCommunicationDeviceRemoval removing client: "
+                            + crc.toString());
+                }
+                // Cancelling the route for this client will remove it from the stack and update
+                // the communication route.
+                CommunicationDeviceInfo deviceInfo = new CommunicationDeviceInfo(
+                        crc.getBinder(), crc.getUid(), device, false,
+                        BtHelper.SCO_MODE_UNDEFINED, "onCheckCommunicationDeviceRemoval",
+                        false, crc.isPrivileged());
+                postSetCommunicationDeviceForClient(deviceInfo);
+            }
+        }
+    }
     /* package */ static List<AudioDeviceInfo> getAvailableCommunicationDevices() {
         ArrayList<AudioDeviceInfo> commDevices = new ArrayList<>();
         AudioDeviceInfo[] allDevices =
@@ -1107,26 +1148,26 @@
         sendLMsgNoDelay(MSG_L_BLUETOOTH_DEVICE_CONFIG_CHANGE, SENDMSG_QUEUE, info);
     }
 
-    /*package*/ void startBluetoothScoForClient(IBinder cb, int pid, int scoAudioMode,
-                @NonNull String eventSource) {
+    /*package*/ void startBluetoothScoForClient(IBinder cb, int uid, int scoAudioMode,
+                                                boolean isPrivileged, @NonNull String eventSource) {
 
         if (AudioService.DEBUG_COMM_RTE) {
-            Log.v(TAG, "startBluetoothScoForClient, pid: " + pid);
+            Log.v(TAG, "startBluetoothScoForClient, uid: " + uid);
         }
         postSetCommunicationDeviceForClient(new CommunicationDeviceInfo(
-                cb, pid, new AudioDeviceAttributes(AudioSystem.DEVICE_OUT_BLUETOOTH_SCO, ""),
-                true, scoAudioMode, eventSource, false));
+                cb, uid, new AudioDeviceAttributes(AudioSystem.DEVICE_OUT_BLUETOOTH_SCO, ""),
+                true, scoAudioMode, eventSource, false, isPrivileged));
     }
 
     /*package*/ void stopBluetoothScoForClient(
-                        IBinder cb, int pid, @NonNull String eventSource) {
+                        IBinder cb, int uid, boolean isPrivileged, @NonNull String eventSource) {
 
         if (AudioService.DEBUG_COMM_RTE) {
-            Log.v(TAG, "stopBluetoothScoForClient, pid: " + pid);
+            Log.v(TAG, "stopBluetoothScoForClient, uid: " + uid);
         }
         postSetCommunicationDeviceForClient(new CommunicationDeviceInfo(
-                cb, pid, new AudioDeviceAttributes(AudioSystem.DEVICE_OUT_BLUETOOTH_SCO, ""),
-                false, BtHelper.SCO_MODE_UNDEFINED, eventSource, false));
+                cb, uid, new AudioDeviceAttributes(AudioSystem.DEVICE_OUT_BLUETOOTH_SCO, ""),
+                false, BtHelper.SCO_MODE_UNDEFINED, eventSource, false, isPrivileged));
     }
 
     /*package*/ int setPreferredDevicesForStrategySync(int strategy,
@@ -1367,22 +1408,24 @@
 
     /*package*/ static final class CommunicationDeviceInfo {
         final @NonNull IBinder mCb; // Identifies the requesting client for death handler
-        final int mPid; // Requester process ID
+        final int mUid; // Requester UID
         final @Nullable AudioDeviceAttributes mDevice; // Device being set or reset.
         final boolean mOn; // true if setting, false if resetting
         final int mScoAudioMode; // only used for SCO: requested audio mode
+        final boolean mIsPrivileged; // true if the client app has MODIFY_PHONE_STATE permission
         final @NonNull String mEventSource; // caller identifier for logging
         boolean mWaitForStatus; // true if the caller waits for a completion status (API dependent)
         boolean mStatus = false; // completion status only used if mWaitForStatus is true
 
-        CommunicationDeviceInfo(@NonNull IBinder cb, int pid,
+        CommunicationDeviceInfo(@NonNull IBinder cb, int uid,
                 @Nullable AudioDeviceAttributes device, boolean on, int scoAudioMode,
-                @NonNull String eventSource, boolean waitForStatus) {
+                @NonNull String eventSource, boolean waitForStatus, boolean isPrivileged) {
             mCb = cb;
-            mPid = pid;
+            mUid = uid;
             mDevice = device;
             mOn = on;
             mScoAudioMode = scoAudioMode;
+            mIsPrivileged = isPrivileged;
             mEventSource = eventSource;
             mWaitForStatus = waitForStatus;
         }
@@ -1401,16 +1444,17 @@
             }
 
             return mCb.equals(((CommunicationDeviceInfo) o).mCb)
-                    && mPid == ((CommunicationDeviceInfo) o).mPid;
+                    && mUid == ((CommunicationDeviceInfo) o).mUid;
         }
 
         @Override
         public String toString() {
             return "CommunicationDeviceInfo mCb=" + mCb.toString()
-                    + " mPid=" + mPid
+                    + " mUid=" + mUid
                     + " mDevice=[" + (mDevice != null ? mDevice.toString() : "null") + "]"
                     + " mOn=" + mOn
                     + " mScoAudioMode=" + mScoAudioMode
+                    + " mIsPrivileged=" + mIsPrivileged
                     + " mEventSource=" + mEventSource
                     + " mWaitForStatus=" + mWaitForStatus
                     + " mStatus=" + mStatus;
@@ -1440,7 +1484,7 @@
         }
     }
 
-    /*package*/ boolean handleDeviceConnection(AudioDeviceAttributes attributes,
+    /*package*/ boolean handleDeviceConnection(@NonNull AudioDeviceAttributes attributes,
                                 boolean connect, @Nullable BluetoothDevice btDevice) {
         synchronized (mDeviceStateLock) {
             return mDeviceInventory.handleDeviceConnection(
@@ -1507,8 +1551,7 @@
 
         pw.println("\n" + prefix + "Communication route clients:");
         mCommunicationRouteClients.forEach((cl) -> {
-            pw.println("  " + prefix + "pid: " + cl.getPid() + " device: "
-                        + cl.getDevice() + " cb: " + cl.getBinder()); });
+            pw.println("  " + prefix + cl.toString()); });
 
         pw.println("\n" + prefix + "Computed Preferred communication device: "
                 +  preferredCommunicationDevice());
@@ -1850,6 +1893,15 @@
                     final BluetoothDevice btDevice = (BluetoothDevice) msg.obj;
                     BtHelper.onNotifyPreferredAudioProfileApplied(btDevice);
                 } break;
+
+                case MSG_L_CHECK_COMMUNICATION_DEVICE_REMOVAL: {
+                    synchronized (mSetModeLock) {
+                        synchronized (mDeviceStateLock) {
+                            onCheckCommunicationDeviceRemoval((AudioDeviceAttributes) msg.obj);
+                        }
+                    }
+                } break;
+
                 default:
                     Log.wtf(TAG, "Invalid message " + msg.what);
             }
@@ -1926,6 +1978,7 @@
     private static final int MSG_IL_BTLEAUDIO_TIMEOUT = 49;
 
     private static final int MSG_L_NOTIFY_PREFERRED_AUDIOPROFILE_APPLIED = 52;
+    private static final int MSG_L_CHECK_COMMUNICATION_DEVICE_REMOVAL = 53;
 
     private static boolean isMessageHandledUnderWakelock(int msgId) {
         switch(msgId) {
@@ -2101,13 +2154,20 @@
 
     private class CommunicationRouteClient implements IBinder.DeathRecipient {
         private final IBinder mCb;
-        private final int mPid;
+        private final int mUid;
+        private final boolean mIsPrivileged;
         private AudioDeviceAttributes mDevice;
+        private boolean mPlaybackActive;
+        private boolean mRecordingActive;
 
-        CommunicationRouteClient(IBinder cb, int pid, AudioDeviceAttributes device) {
+        CommunicationRouteClient(IBinder cb, int uid, AudioDeviceAttributes device,
+                                 boolean isPrivileged) {
             mCb = cb;
-            mPid = pid;
+            mUid = uid;
             mDevice = device;
+            mIsPrivileged = isPrivileged;
+            mPlaybackActive = mAudioService.isPlaybackActiveForUid(uid);
+            mRecordingActive = mAudioService.isRecordingActiveForUid(uid);
         }
 
         public boolean registerDeathRecipient() {
@@ -2138,13 +2198,38 @@
             return mCb;
         }
 
-        int getPid() {
-            return mPid;
+        int getUid() {
+            return mUid;
+        }
+
+        boolean isPrivileged() {
+            return mIsPrivileged;
         }
 
         AudioDeviceAttributes getDevice() {
             return mDevice;
         }
+
+        public void setPlaybackActive(boolean active) {
+            mPlaybackActive = active;
+        }
+
+        public void setRecordingActive(boolean active) {
+            mRecordingActive = active;
+        }
+
+        public boolean isActive() {
+            return mIsPrivileged || mRecordingActive || mPlaybackActive;
+        }
+
+        @Override
+        public String toString() {
+            return "[CommunicationRouteClient: mUid: " + mUid
+                    + " mDevice: " + mDevice.toString()
+                    + " mIsPrivileged: " + mIsPrivileged
+                    + " mPlaybackActive: " + mPlaybackActive
+                    + " mRecordingActive: " + mRecordingActive + "]";
+        }
     }
 
     // @GuardedBy("mSetModeLock")
@@ -2154,8 +2239,9 @@
             return;
         }
         Log.w(TAG, "Communication client died");
-        setCommunicationRouteForClient(client.getBinder(), client.getPid(), null,
-                BtHelper.SCO_MODE_UNDEFINED, "onCommunicationRouteClientDied");
+        setCommunicationRouteForClient(client.getBinder(), client.getUid(), null,
+                BtHelper.SCO_MODE_UNDEFINED, client.isPrivileged(),
+                "onCommunicationRouteClientDied");
     }
 
     /**
@@ -2242,8 +2328,8 @@
                     + crc + " eventSource: " + eventSource);
         }
         if (crc != null) {
-            setCommunicationRouteForClient(crc.getBinder(), crc.getPid(), crc.getDevice(),
-                    BtHelper.SCO_MODE_UNDEFINED, eventSource);
+            setCommunicationRouteForClient(crc.getBinder(), crc.getUid(), crc.getDevice(),
+                    BtHelper.SCO_MODE_UNDEFINED, crc.isPrivileged(), eventSource);
         }
     }
 
@@ -2267,6 +2353,7 @@
         dispatchCommunicationDevice();
     }
 
+    @GuardedBy("mDeviceStateLock")
     private CommunicationRouteClient removeCommunicationRouteClient(
                     IBinder cb, boolean unregister) {
         for (CommunicationRouteClient cl : mCommunicationRouteClients) {
@@ -2282,11 +2369,12 @@
     }
 
     @GuardedBy("mDeviceStateLock")
-    private CommunicationRouteClient addCommunicationRouteClient(
-                    IBinder cb, int pid, AudioDeviceAttributes device) {
+    private CommunicationRouteClient addCommunicationRouteClient(IBinder cb, int uid,
+                AudioDeviceAttributes device, boolean isPrivileged) {
         // always insert new request at first position
         removeCommunicationRouteClient(cb, true);
-        CommunicationRouteClient client = new CommunicationRouteClient(cb, pid, device);
+        CommunicationRouteClient client =
+                new CommunicationRouteClient(cb, uid, device, isPrivileged);
         if (client.registerDeathRecipient()) {
             mCommunicationRouteClients.add(0, client);
             return client;
@@ -2295,9 +2383,9 @@
     }
 
     @GuardedBy("mDeviceStateLock")
-    private CommunicationRouteClient getCommunicationRouteClientForPid(int pid) {
+    private CommunicationRouteClient getCommunicationRouteClientForUid(int uid) {
         for (CommunicationRouteClient cl : mCommunicationRouteClients) {
-            if (cl.getPid() == pid) {
+            if (cl.getUid() == uid) {
                 return cl;
             }
         }
@@ -2330,6 +2418,45 @@
         return device;
     }
 
+    void updateCommunicationRouteClientsActivity(
+            List<AudioPlaybackConfiguration> playbackConfigs,
+            List<AudioRecordingConfiguration> recordConfigs) {
+        synchronized (mSetModeLock) {
+            synchronized (mDeviceStateLock) {
+                boolean updateCommunicationRoute = false;
+                for (CommunicationRouteClient crc : mCommunicationRouteClients) {
+                    boolean wasActive = crc.isActive();
+                    if (playbackConfigs != null) {
+                        crc.setPlaybackActive(false);
+                        for (AudioPlaybackConfiguration config : playbackConfigs) {
+                            if (config.getClientUid() == crc.getUid()
+                                    && config.isActive()) {
+                                crc.setPlaybackActive(true);
+                                break;
+                            }
+                        }
+                    }
+                    if (recordConfigs != null) {
+                        crc.setRecordingActive(false);
+                        for (AudioRecordingConfiguration config : recordConfigs) {
+                            if (config.getClientUid() == crc.getUid()
+                                    && !config.isClientSilenced()) {
+                                crc.setRecordingActive(true);
+                                break;
+                            }
+                        }
+                    }
+                    if (wasActive != crc.isActive()) {
+                        updateCommunicationRoute = true;
+                    }
+                }
+                if (updateCommunicationRoute) {
+                    postUpdateCommunicationRouteClient("updateCommunicationRouteClientsActivity");
+                }
+            }
+        }
+    }
+
     @Nullable UUID getDeviceSensorUuid(AudioDeviceAttributes device) {
         synchronized (mDeviceStateLock) {
             return mDeviceInventory.getDeviceSensorUuid(device);
diff --git a/services/core/java/com/android/server/audio/AudioDeviceInventory.java b/services/core/java/com/android/server/audio/AudioDeviceInventory.java
index 0c7f11f..b70e11d 100644
--- a/services/core/java/com/android/server/audio/AudioDeviceInventory.java
+++ b/services/core/java/com/android/server/audio/AudioDeviceInventory.java
@@ -1245,8 +1245,9 @@
      * @param btDevice the corresponding Bluetooth device when relevant.
      * @return false if an error was reported by AudioSystem
      */
-    /*package*/ boolean handleDeviceConnection(AudioDeviceAttributes attributes, boolean connect,
-            boolean isForTesting, @Nullable BluetoothDevice btDevice) {
+    /*package*/ boolean handleDeviceConnection(@NonNull AudioDeviceAttributes attributes,
+                                               boolean connect, boolean isForTesting,
+                                               @Nullable BluetoothDevice btDevice) {
         int device = attributes.getInternalType();
         String address = attributes.getAddress();
         String deviceName = attributes.getName();
@@ -1297,6 +1298,7 @@
                         AudioSystem.DEVICE_STATE_UNAVAILABLE, AudioSystem.AUDIO_FORMAT_DEFAULT);
                 // always remove even if disconnection failed
                 mConnectedDevices.remove(deviceKey);
+                mDeviceBroker.postCheckCommunicationDeviceRemoval(attributes);
                 status = true;
             }
             if (status) {
@@ -1801,8 +1803,9 @@
 
         // device to remove was visible by APM, update APM
         mDeviceBroker.clearAvrcpAbsoluteVolumeSupported();
-        final int res = mAudioSystem.setDeviceConnectionState(new AudioDeviceAttributes(
-                AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, address),
+        AudioDeviceAttributes ada = new AudioDeviceAttributes(
+                AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, address);
+        final int res = mAudioSystem.setDeviceConnectionState(ada,
                 AudioSystem.DEVICE_STATE_UNAVAILABLE, a2dpCodec);
 
         if (res != AudioSystem.AUDIO_STATUS_OK) {
@@ -1816,11 +1819,13 @@
                     "A2DP device addr=" + address + " made unavailable")).printLog(TAG));
         }
         mApmConnectedDevices.remove(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP);
+
         // Remove A2DP routes as well
         setCurrentAudioRouteNameIfPossible(null, true /*fromA2dp*/);
         mmi.record();
         updateBluetoothPreferredModes_l(null /*connectedDevice*/);
         purgeDevicesRoles_l();
+        mDeviceBroker.postCheckCommunicationDeviceRemoval(ada);
     }
 
     @GuardedBy("mDevicesLock")
@@ -1855,12 +1860,14 @@
 
     @GuardedBy("mDevicesLock")
     private void makeA2dpSrcUnavailable(String address) {
-        mAudioSystem.setDeviceConnectionState(new AudioDeviceAttributes(
-                AudioSystem.DEVICE_IN_BLUETOOTH_A2DP, address),
+        AudioDeviceAttributes ada = new AudioDeviceAttributes(
+                AudioSystem.DEVICE_IN_BLUETOOTH_A2DP, address);
+        mAudioSystem.setDeviceConnectionState(ada,
                 AudioSystem.DEVICE_STATE_UNAVAILABLE,
                 AudioSystem.AUDIO_FORMAT_DEFAULT);
         mConnectedDevices.remove(
                 DeviceInfo.makeDeviceListKey(AudioSystem.DEVICE_IN_BLUETOOTH_A2DP, address));
+        mDeviceBroker.postCheckCommunicationDeviceRemoval(ada);
     }
 
     @GuardedBy("mDevicesLock")
@@ -1893,8 +1900,9 @@
 
     @GuardedBy("mDevicesLock")
     private void makeHearingAidDeviceUnavailable(String address) {
-        mAudioSystem.setDeviceConnectionState(new AudioDeviceAttributes(
-                AudioSystem.DEVICE_OUT_HEARING_AID, address),
+        AudioDeviceAttributes ada = new AudioDeviceAttributes(
+                AudioSystem.DEVICE_OUT_HEARING_AID, address);
+        mAudioSystem.setDeviceConnectionState(ada,
                 AudioSystem.DEVICE_STATE_UNAVAILABLE,
                 AudioSystem.AUDIO_FORMAT_DEFAULT);
         mConnectedDevices.remove(
@@ -1906,6 +1914,7 @@
                 .set(MediaMetrics.Property.DEVICE,
                         AudioSystem.getDeviceName(AudioSystem.DEVICE_OUT_HEARING_AID))
                 .record();
+        mDeviceBroker.postCheckCommunicationDeviceRemoval(ada);
     }
 
     /**
@@ -2002,9 +2011,10 @@
 
     @GuardedBy("mDevicesLock")
     private void makeLeAudioDeviceUnavailableNow(String address, int device) {
+        AudioDeviceAttributes ada = null;
         if (device != AudioSystem.DEVICE_NONE) {
-            final int res = AudioSystem.setDeviceConnectionState(new AudioDeviceAttributes(
-                    device, address),
+            ada = new AudioDeviceAttributes(device, address);
+            final int res = AudioSystem.setDeviceConnectionState(ada,
                     AudioSystem.DEVICE_STATE_UNAVAILABLE,
                     AudioSystem.AUDIO_FORMAT_DEFAULT);
 
@@ -2024,6 +2034,9 @@
         setCurrentAudioRouteNameIfPossible(null, false /*fromA2dp*/);
         updateBluetoothPreferredModes_l(null /*connectedDevice*/);
         purgeDevicesRoles_l();
+        if (ada != null) {
+            mDeviceBroker.postCheckCommunicationDeviceRemoval(ada);
+        }
     }
 
     @GuardedBy("mDevicesLock")
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index 53ed38e..b70b2b3 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -4262,22 +4262,41 @@
         // When the audio mode owner becomes active, replace any delayed MSG_UPDATE_AUDIO_MODE
         // and request an audio mode update immediately. Upon any other change, queue the message
         // and request an audio mode update after a grace period.
+        updateAudioModeHandlers(
+                configs /* playbackConfigs */, null /* recordConfigs */);
+        mDeviceBroker.updateCommunicationRouteClientsActivity(
+                configs /* playbackConfigs */, null /* recordConfigs */);
+    }
+
+    void updateAudioModeHandlers(List<AudioPlaybackConfiguration> playbackConfigs,
+                                 List<AudioRecordingConfiguration> recordConfigs) {
         synchronized (mDeviceBroker.mSetModeLock) {
             boolean updateAudioMode = false;
             int existingMsgPolicy = SENDMSG_QUEUE;
             int delay = CHECK_MODE_FOR_UID_PERIOD_MS;
             for (SetModeDeathHandler h : mSetModeDeathHandlers) {
                 boolean wasActive = h.isActive();
-                h.setPlaybackActive(false);
-                for (AudioPlaybackConfiguration config : configs) {
-                    final int usage = config.getAudioAttributes().getUsage();
-                    if (config.getClientUid() == h.getUid()
-                            && (usage == AudioAttributes.USAGE_VOICE_COMMUNICATION
+                if (playbackConfigs != null) {
+                    h.setPlaybackActive(false);
+                    for (AudioPlaybackConfiguration config : playbackConfigs) {
+                        final int usage = config.getAudioAttributes().getUsage();
+                        if (config.getClientUid() == h.getUid()
+                                && (usage == AudioAttributes.USAGE_VOICE_COMMUNICATION
                                 || usage == AudioAttributes.USAGE_VOICE_COMMUNICATION_SIGNALLING)
-                            && config.getPlayerState()
-                                == AudioPlaybackConfiguration.PLAYER_STATE_STARTED) {
-                        h.setPlaybackActive(true);
-                        break;
+                                && config.isActive()) {
+                            h.setPlaybackActive(true);
+                            break;
+                        }
+                    }
+                }
+                if (recordConfigs != null) {
+                    h.setRecordingActive(false);
+                    for (AudioRecordingConfiguration config : recordConfigs) {
+                        if (config.getClientUid() == h.getUid() && !config.isClientSilenced()
+                                && config.getAudioSource() == AudioSource.VOICE_COMMUNICATION) {
+                            h.setRecordingActive(true);
+                            break;
+                        }
                     }
                 }
                 if (wasActive != h.isActive()) {
@@ -4315,38 +4334,10 @@
         // When the audio mode owner becomes active, replace any delayed MSG_UPDATE_AUDIO_MODE
         // and request an audio mode update immediately. Upon any other change, queue the message
         // and request an audio mode update after a grace period.
-        synchronized (mDeviceBroker.mSetModeLock) {
-            boolean updateAudioMode = false;
-            int existingMsgPolicy = SENDMSG_QUEUE;
-            int delay = CHECK_MODE_FOR_UID_PERIOD_MS;
-            for (SetModeDeathHandler h : mSetModeDeathHandlers) {
-                boolean wasActive = h.isActive();
-                h.setRecordingActive(false);
-                for (AudioRecordingConfiguration config : configs) {
-                    if (config.getClientUid() == h.getUid()
-                            && config.getAudioSource() == AudioSource.VOICE_COMMUNICATION) {
-                        h.setRecordingActive(true);
-                        break;
-                    }
-                }
-                if (wasActive != h.isActive()) {
-                    updateAudioMode = true;
-                    if (h.isActive() && h == getAudioModeOwnerHandler()) {
-                        existingMsgPolicy = SENDMSG_REPLACE;
-                        delay = 0;
-                    }
-                }
-            }
-            if (updateAudioMode) {
-                sendMsg(mAudioHandler,
-                        MSG_UPDATE_AUDIO_MODE,
-                        existingMsgPolicy,
-                        AudioSystem.MODE_CURRENT,
-                        android.os.Process.myPid(),
-                        mContext.getPackageName(),
-                        delay);
-            }
-        }
+        updateAudioModeHandlers(
+                null /* playbackConfigs */, configs /* recordConfigs */);
+        mDeviceBroker.updateCommunicationRouteClientsActivity(
+                null /* playbackConfigs */, configs /* recordConfigs */);
     }
 
     private void dumpAudioMode(PrintWriter pw) {
@@ -6299,10 +6290,12 @@
                             ? MediaMetrics.Value.CONNECTED : MediaMetrics.Value.DISCONNECTED)
                     .record();
         }
-
+        final boolean isPrivileged = mContext.checkCallingOrSelfPermission(
+                android.Manifest.permission.MODIFY_PHONE_STATE)
+                == PackageManager.PERMISSION_GRANTED;
         final long ident = Binder.clearCallingIdentity();
         try {
-            return mDeviceBroker.setCommunicationDevice(cb, pid, device, eventSource);
+            return mDeviceBroker.setCommunicationDevice(cb, uid, device, isPrivileged, eventSource);
         } finally {
             Binder.restoreCallingIdentity(ident);
         }
@@ -6348,6 +6341,9 @@
         if (!checkAudioSettingsPermission("setSpeakerphoneOn()")) {
             return;
         }
+        final boolean isPrivileged = mContext.checkCallingOrSelfPermission(
+                android.Manifest.permission.MODIFY_PHONE_STATE)
+                == PackageManager.PERMISSION_GRANTED;
 
         // for logging only
         final int uid = Binder.getCallingUid();
@@ -6363,9 +6359,10 @@
                 .set(MediaMetrics.Property.STATE, on
                         ? MediaMetrics.Value.ON : MediaMetrics.Value.OFF)
                 .record();
+
         final long ident = Binder.clearCallingIdentity();
         try {
-            mDeviceBroker.setSpeakerphoneOn(cb, pid, on, eventSource);
+            mDeviceBroker.setSpeakerphoneOn(cb, uid, on, isPrivileged, eventSource);
         } finally {
             Binder.restoreCallingIdentity(ident);
         }
@@ -6490,7 +6487,7 @@
                 .set(MediaMetrics.Property.SCO_AUDIO_MODE,
                         BtHelper.scoAudioModeToString(scoAudioMode))
                 .record();
-        startBluetoothScoInt(cb, pid, scoAudioMode, eventSource);
+        startBluetoothScoInt(cb, uid, scoAudioMode, eventSource);
 
     }
 
@@ -6513,10 +6510,10 @@
                 .set(MediaMetrics.Property.SCO_AUDIO_MODE,
                         BtHelper.scoAudioModeToString(BtHelper.SCO_MODE_VIRTUAL_CALL))
                 .record();
-        startBluetoothScoInt(cb, pid, BtHelper.SCO_MODE_VIRTUAL_CALL, eventSource);
+        startBluetoothScoInt(cb, uid, BtHelper.SCO_MODE_VIRTUAL_CALL, eventSource);
     }
 
-    void startBluetoothScoInt(IBinder cb, int pid, int scoAudioMode, @NonNull String eventSource) {
+    void startBluetoothScoInt(IBinder cb, int uid, int scoAudioMode, @NonNull String eventSource) {
         MediaMetrics.Item mmi = new MediaMetrics.Item(MediaMetrics.Name.AUDIO_BLUETOOTH)
                 .set(MediaMetrics.Property.EVENT, "startBluetoothScoInt")
                 .set(MediaMetrics.Property.SCO_AUDIO_MODE,
@@ -6527,9 +6524,13 @@
             mmi.set(MediaMetrics.Property.EARLY_RETURN, "permission or systemReady").record();
             return;
         }
+        final boolean isPrivileged = mContext.checkCallingOrSelfPermission(
+                android.Manifest.permission.MODIFY_PHONE_STATE)
+                == PackageManager.PERMISSION_GRANTED;
         final long ident = Binder.clearCallingIdentity();
         try {
-            mDeviceBroker.startBluetoothScoForClient(cb, pid, scoAudioMode, eventSource);
+            mDeviceBroker.startBluetoothScoForClient(
+                    cb, uid, scoAudioMode, isPrivileged, eventSource);
         } finally {
             Binder.restoreCallingIdentity(ident);
         }
@@ -6547,9 +6548,12 @@
         final String eventSource =  new StringBuilder("stopBluetoothSco()")
                 .append(") from u/pid:").append(uid).append("/")
                 .append(pid).toString();
+        final boolean isPrivileged = mContext.checkCallingOrSelfPermission(
+                android.Manifest.permission.MODIFY_PHONE_STATE)
+                == PackageManager.PERMISSION_GRANTED;
         final long ident = Binder.clearCallingIdentity();
         try {
-            mDeviceBroker.stopBluetoothScoForClient(cb, pid, eventSource);
+            mDeviceBroker.stopBluetoothScoForClient(cb, uid, isPrivileged, eventSource);
         } finally {
             Binder.restoreCallingIdentity(ident);
         }
@@ -9284,8 +9288,8 @@
                             break;
                         }
                         boolean wasActive = h.isActive();
-                        h.setPlaybackActive(mPlaybackMonitor.isPlaybackActiveForUid(h.getUid()));
-                        h.setRecordingActive(mRecordMonitor.isRecordingActiveForUid(h.getUid()));
+                        h.setPlaybackActive(isPlaybackActiveForUid(h.getUid()));
+                        h.setRecordingActive(isRecordingActiveForUid(h.getUid()));
                         if (wasActive != h.isActive()) {
                             onUpdateAudioMode(AudioSystem.MODE_CURRENT, android.os.Process.myPid(),
                                     mContext.getPackageName(), false /*force*/);
@@ -12378,6 +12382,16 @@
         }
     }
 
+    /* package */
+    boolean isPlaybackActiveForUid(int uid) {
+        return mPlaybackMonitor.isPlaybackActiveForUid(uid);
+    }
+
+    /* package */
+    boolean isRecordingActiveForUid(int uid) {
+        return mRecordMonitor.isRecordingActiveForUid(uid);
+    }
+
     //======================
     // Audio device management
     //======================
diff --git a/services/core/java/com/android/server/audio/RecordingActivityMonitor.java b/services/core/java/com/android/server/audio/RecordingActivityMonitor.java
index 652ea52..4332fdd 100644
--- a/services/core/java/com/android/server/audio/RecordingActivityMonitor.java
+++ b/services/core/java/com/android/server/audio/RecordingActivityMonitor.java
@@ -227,8 +227,8 @@
         synchronized (mRecordStates) {
             for (RecordingState state : mRecordStates) {
                 // Note: isActiveConfiguration() == true => state.getConfig() != null
-                if (state.isActiveConfiguration()
-                        && state.getConfig().getClientUid() == uid) {
+                if (state.isActiveConfiguration() && state.getConfig().getClientUid() == uid
+                        && !state.getConfig().isClientSilenced()) {
                     return true;
                 }
             }
diff --git a/services/core/java/com/android/server/connectivity/Vpn.java b/services/core/java/com/android/server/connectivity/Vpn.java
index e85eee81..e3262cf 100644
--- a/services/core/java/com/android/server/connectivity/Vpn.java
+++ b/services/core/java/com/android/server/connectivity/Vpn.java
@@ -22,7 +22,6 @@
 import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_METERED;
 import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_VPN;
 import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
-import static android.net.NetworkCapabilities.TRANSPORT_VPN;
 import static android.net.RouteInfo.RTN_THROW;
 import static android.net.RouteInfo.RTN_UNREACHABLE;
 import static android.net.VpnManager.NOTIFICATION_CHANNEL_VPN;
@@ -280,15 +279,22 @@
     private static final int VPN_DEFAULT_SCORE = 101;
 
     /**
-     * The reset session timer for data stall. If a session has not successfully revalidated after
-     * the delay, the session will be torn down and restarted in an attempt to recover. Delay
+     * The recovery timer for data stall. If a session has not successfully revalidated after
+     * the delay, the session will perform MOBIKE or be restarted in an attempt to recover. Delay
      * counter is reset on successful validation only.
      *
+     * <p>The first {@code MOBIKE_RECOVERY_ATTEMPT} timers are used for performing MOBIKE.
+     * System will perform session reset for the remaining timers.
      * <p>If retries have exceeded the length of this array, the last entry in the array will be
      * used as a repeating interval.
      */
-    private static final long[] DATA_STALL_RESET_DELAYS_SEC = {30L, 60L, 120L, 240L, 480L, 960L};
-
+    // TODO: use ms instead to speed up the test.
+    private static final long[] DATA_STALL_RECOVERY_DELAYS_SEC =
+            {1L, 5L, 30L, 60L, 120L, 240L, 480L, 960L};
+    /**
+     * Maximum attempts to perform MOBIKE when the network is bad.
+     */
+    private static final int MAX_MOBIKE_RECOVERY_ATTEMPT = 2;
     /**
      * The initial token value of IKE session.
      */
@@ -380,6 +386,7 @@
     private final INetworkManagementService mNms;
     private final INetd mNetd;
     @VisibleForTesting
+    @GuardedBy("this")
     protected VpnConfig mConfig;
     private final NetworkProvider mNetworkProvider;
     @VisibleForTesting
@@ -392,7 +399,6 @@
     private final UserManager mUserManager;
 
     private final VpnProfileStore mVpnProfileStore;
-    protected boolean mDataStallSuspected = false;
 
     @VisibleForTesting
     VpnProfileStore getVpnProfileStore() {
@@ -685,14 +691,14 @@
         }
 
         /**
-         * Get the length of time to wait before resetting the ike session when a data stall is
-         * suspected.
+         * Get the length of time to wait before perform data stall recovery when the validation
+         * result is bad.
          */
-        public long getDataStallResetSessionSeconds(int count) {
-            if (count >= DATA_STALL_RESET_DELAYS_SEC.length) {
-                return DATA_STALL_RESET_DELAYS_SEC[DATA_STALL_RESET_DELAYS_SEC.length - 1];
+        public long getValidationFailRecoverySeconds(int count) {
+            if (count >= DATA_STALL_RECOVERY_DELAYS_SEC.length) {
+                return DATA_STALL_RECOVERY_DELAYS_SEC[DATA_STALL_RECOVERY_DELAYS_SEC.length - 1];
             } else {
-                return DATA_STALL_RESET_DELAYS_SEC[count];
+                return DATA_STALL_RECOVERY_DELAYS_SEC[count];
             }
         }
 
@@ -1598,6 +1604,8 @@
         return network;
     }
 
+    // TODO : this is not synchronized(this) but reads from mConfig, which is dangerous
+    // This file makes an effort to avoid partly initializing mConfig, but this is still not great
     private LinkProperties makeLinkProperties() {
         // The design of disabling IPv6 is only enabled for IKEv2 VPN because it needs additional
         // logic to handle IPv6 only VPN, and the IPv6 only VPN may be restarted when its MTU
@@ -1679,6 +1687,7 @@
      * registering a new NetworkAgent. This is not always possible if the new VPN configuration
      * has certain changes, in which case this method would just return {@code false}.
      */
+    // TODO : this method is not synchronized(this) but reads from mConfig
     private boolean updateLinkPropertiesInPlaceIfPossible(NetworkAgent agent, VpnConfig oldConfig) {
         // NetworkAgentConfig cannot be updated without registering a new NetworkAgent.
         // Strictly speaking, bypassability is affected by lockdown and therefore it's possible
@@ -2269,7 +2278,12 @@
      */
     public synchronized VpnConfig getVpnConfig() {
         enforceControlPermission();
-        return mConfig;
+        // Constructor of VpnConfig cannot take a null parameter. Return null directly if mConfig is
+        // null
+        if (mConfig == null) return null;
+        // mConfig is guarded by "this" and can be modified by another thread as soon as
+        // this method returns, so this method must return a copy.
+        return new VpnConfig(mConfig);
     }
 
     @Deprecated
@@ -2315,6 +2329,7 @@
         }
     };
 
+    @GuardedBy("this")
     private void cleanupVpnStateLocked() {
         mStatusIntent = null;
         resetNetworkCapabilities();
@@ -2837,9 +2852,7 @@
         }
 
         final boolean isLegacyVpn = mVpnRunner instanceof LegacyVpnRunner;
-
         mVpnRunner.exit();
-        mVpnRunner = null;
 
         // LegacyVpn uses daemons that must be shut down before new ones are brought up.
         // The same limitation does not apply to Platform VPNs.
@@ -3044,7 +3057,6 @@
 
         @Nullable private IkeSessionWrapper mSession;
         @Nullable private IkeSessionConnectionInfo mIkeConnectionInfo;
-        @Nullable private VpnConnectivityDiagnosticsCallback mDiagnosticsCallback;
 
         // mMobikeEnabled can only be updated after IKE AUTH is finished.
         private boolean mMobikeEnabled = false;
@@ -3055,7 +3067,7 @@
          * <p>This variable controls the retry delay, and is reset when the VPN pass network
          * validation.
          */
-        private int mDataStallRetryCount = 0;
+        private int mValidationFailRetryCount = 0;
 
         /**
          * The number of attempts since the last successful connection.
@@ -3084,6 +3096,7 @@
                     }
         };
 
+        // GuardedBy("Vpn.this") (annotation can't be applied to constructor)
         IkeV2VpnRunner(
                 @NonNull Ikev2VpnProfile profile, @NonNull ScheduledThreadPoolExecutor executor) {
             super(TAG);
@@ -3136,15 +3149,6 @@
                 mConnectivityManager.registerSystemDefaultNetworkCallback(mNetworkCallback,
                         new Handler(mLooper));
             }
-
-            // DiagnosticsCallback may return more than one alive VPNs, but VPN will filter based on
-            // Network object.
-            final NetworkRequest diagRequest = new NetworkRequest.Builder()
-                    .addTransportType(TRANSPORT_VPN)
-                    .removeCapability(NET_CAPABILITY_NOT_VPN).build();
-            mDiagnosticsCallback = new VpnConnectivityDiagnosticsCallback();
-            mConnectivityDiagnosticsManager.registerConnectivityDiagnosticsCallback(
-                    diagRequest, mExecutor, mDiagnosticsCallback);
         }
 
         private boolean isActiveNetwork(@Nullable Network network) {
@@ -3710,11 +3714,14 @@
         }
 
         public void updateVpnTransportInfoAndNetCap(int keepaliveDelaySec) {
-            final VpnTransportInfo info = new VpnTransportInfo(
-                    getActiveVpnType(),
-                    mConfig.session,
-                    mConfig.allowBypass && !mLockdown,
-                    areLongLivedTcpConnectionsExpensive(keepaliveDelaySec));
+            final VpnTransportInfo info;
+            synchronized (Vpn.this) {
+                info = new VpnTransportInfo(
+                        getActiveVpnType(),
+                        mConfig.session,
+                        mConfig.allowBypass && !mLockdown,
+                        areLongLivedTcpConnectionsExpensive(keepaliveDelaySec));
+            }
             final boolean ncUpdateRequired = !info.equals(mNetworkCapabilities.getTransportInfo());
             if (ncUpdateRequired) {
                 mNetworkCapabilities = new NetworkCapabilities.Builder(mNetworkCapabilities)
@@ -3875,39 +3882,12 @@
             }
         }
 
-        class VpnConnectivityDiagnosticsCallback
-                extends ConnectivityDiagnosticsManager.ConnectivityDiagnosticsCallback {
-            // The callback runs in the executor thread.
-            @Override
-            public void onDataStallSuspected(
-                    ConnectivityDiagnosticsManager.DataStallReport report) {
-                synchronized (Vpn.this) {
-                    // Ignore stale runner.
-                    if (mVpnRunner != Vpn.IkeV2VpnRunner.this) return;
-
-                    // Handle the report only for current VPN network. If data stall is already
-                    // reported, ignoring the other reports. It means that the stall is not
-                    // recovered by MOBIKE and should be on the way to reset the ike session.
-                    if (mNetworkAgent != null
-                            && mNetworkAgent.getNetwork().equals(report.getNetwork())
-                            && !mDataStallSuspected) {
-                        Log.d(TAG, "Data stall suspected");
-
-                        // Trigger MOBIKE.
-                        maybeMigrateIkeSessionAndUpdateVpnTransportInfo(mActiveNetwork);
-                        mDataStallSuspected = true;
-                    }
-                }
-            }
-        }
-
         public void onValidationStatus(int status) {
             mEventChanges.log("[Validation] validation status " + status);
             if (status == NetworkAgent.VALIDATION_STATUS_VALID) {
                 // No data stall now. Reset it.
                 mExecutor.execute(() -> {
-                    mDataStallSuspected = false;
-                    mDataStallRetryCount = 0;
+                    mValidationFailRetryCount = 0;
                     if (mScheduledHandleDataStallFuture != null) {
                         Log.d(TAG, "Recovered from stall. Cancel pending reset action.");
                         mScheduledHandleDataStallFuture.cancel(false /* mayInterruptIfRunning */);
@@ -3918,8 +3898,21 @@
                 // Skip other invalid status if the scheduled recovery exists.
                 if (mScheduledHandleDataStallFuture != null) return;
 
+                if (mValidationFailRetryCount < MAX_MOBIKE_RECOVERY_ATTEMPT) {
+                    Log.d(TAG, "Validation failed");
+
+                    // Trigger MOBIKE to recover first.
+                    mExecutor.schedule(() -> {
+                        maybeMigrateIkeSessionAndUpdateVpnTransportInfo(mActiveNetwork);
+                    }, mDeps.getValidationFailRecoverySeconds(mValidationFailRetryCount++),
+                            TimeUnit.SECONDS);
+                    return;
+                }
+
+                // Data stall is not recovered by MOBIKE. Try to reset session to recover it.
                 mScheduledHandleDataStallFuture = mExecutor.schedule(() -> {
-                    if (mDataStallSuspected) {
+                    // Only perform the recovery when the network is still bad.
+                    if (mValidationFailRetryCount > 0) {
                         Log.d(TAG, "Reset session to recover stalled network");
                         // This will reset old state if it exists.
                         startIkeSession(mActiveNetwork);
@@ -3928,7 +3921,9 @@
                     // Reset mScheduledHandleDataStallFuture since it's already run on executor
                     // thread.
                     mScheduledHandleDataStallFuture = null;
-                }, mDeps.getDataStallResetSessionSeconds(mDataStallRetryCount++), TimeUnit.SECONDS);
+                    // TODO: compute the delay based on the last recovery timestamp
+                }, mDeps.getValidationFailRecoverySeconds(mValidationFailRetryCount++),
+                        TimeUnit.SECONDS);
             }
         }
 
@@ -4220,7 +4215,7 @@
          * consistency of the Ikev2VpnRunner fields.
          */
         private void disconnectVpnRunner() {
-            mEventChanges.log("[VPNRunner] Disconnect runner, underlying network" + mActiveNetwork);
+            mEventChanges.log("[VPNRunner] Disconnect runner, underlying net " + mActiveNetwork);
             mActiveNetwork = null;
             mUnderlyingNetworkCapabilities = null;
             mUnderlyingLinkProperties = null;
@@ -4231,8 +4226,6 @@
             mCarrierConfigManager.unregisterCarrierConfigChangeListener(
                     mCarrierConfigChangeListener);
             mConnectivityManager.unregisterNetworkCallback(mNetworkCallback);
-            mConnectivityDiagnosticsManager.unregisterConnectivityDiagnosticsCallback(
-                    mDiagnosticsCallback);
             clearVpnNetworkPreference(mSessionKey);
 
             mExecutor.shutdown();
@@ -4293,6 +4286,7 @@
             }
         };
 
+        // GuardedBy("Vpn.this") (annotation can't be applied to constructor)
         LegacyVpnRunner(VpnConfig config, String[] racoon, String[] mtpd, VpnProfile profile) {
             super(TAG);
             if (racoon == null && mtpd == null) {
@@ -4500,46 +4494,46 @@
                 }
 
                 // Set the interface and the addresses in the config.
-                mConfig.interfaze = parameters[0].trim();
-
-                mConfig.addLegacyAddresses(parameters[1]);
-                // Set the routes if they are not set in the config.
-                if (mConfig.routes == null || mConfig.routes.isEmpty()) {
-                    mConfig.addLegacyRoutes(parameters[2]);
-                }
-
-                // Set the DNS servers if they are not set in the config.
-                if (mConfig.dnsServers == null || mConfig.dnsServers.size() == 0) {
-                    String dnsServers = parameters[3].trim();
-                    if (!dnsServers.isEmpty()) {
-                        mConfig.dnsServers = Arrays.asList(dnsServers.split(" "));
-                    }
-                }
-
-                // Set the search domains if they are not set in the config.
-                if (mConfig.searchDomains == null || mConfig.searchDomains.size() == 0) {
-                    String searchDomains = parameters[4].trim();
-                    if (!searchDomains.isEmpty()) {
-                        mConfig.searchDomains = Arrays.asList(searchDomains.split(" "));
-                    }
-                }
-
-                // Add a throw route for the VPN server endpoint, if one was specified.
-                if (endpointAddress instanceof Inet4Address) {
-                    mConfig.routes.add(new RouteInfo(
-                            new IpPrefix(endpointAddress, 32), null /*gateway*/,
-                            null /*iface*/, RTN_THROW));
-                } else if (endpointAddress instanceof Inet6Address) {
-                    mConfig.routes.add(new RouteInfo(
-                            new IpPrefix(endpointAddress, 128), null /*gateway*/,
-                            null /*iface*/, RTN_THROW));
-                } else {
-                    Log.e(TAG, "Unknown IP address family for VPN endpoint: "
-                            + endpointAddress);
-                }
-
-                // Here is the last step and it must be done synchronously.
                 synchronized (Vpn.this) {
+                    mConfig.interfaze = parameters[0].trim();
+
+                    mConfig.addLegacyAddresses(parameters[1]);
+                    // Set the routes if they are not set in the config.
+                    if (mConfig.routes == null || mConfig.routes.isEmpty()) {
+                        mConfig.addLegacyRoutes(parameters[2]);
+                    }
+
+                    // Set the DNS servers if they are not set in the config.
+                    if (mConfig.dnsServers == null || mConfig.dnsServers.size() == 0) {
+                        String dnsServers = parameters[3].trim();
+                        if (!dnsServers.isEmpty()) {
+                            mConfig.dnsServers = Arrays.asList(dnsServers.split(" "));
+                        }
+                    }
+
+                    // Set the search domains if they are not set in the config.
+                    if (mConfig.searchDomains == null || mConfig.searchDomains.size() == 0) {
+                        String searchDomains = parameters[4].trim();
+                        if (!searchDomains.isEmpty()) {
+                            mConfig.searchDomains = Arrays.asList(searchDomains.split(" "));
+                        }
+                    }
+
+                    // Add a throw route for the VPN server endpoint, if one was specified.
+                    if (endpointAddress instanceof Inet4Address) {
+                        mConfig.routes.add(new RouteInfo(
+                                new IpPrefix(endpointAddress, 32), null /*gateway*/,
+                                null /*iface*/, RTN_THROW));
+                    } else if (endpointAddress instanceof Inet6Address) {
+                        mConfig.routes.add(new RouteInfo(
+                                new IpPrefix(endpointAddress, 128), null /*gateway*/,
+                                null /*iface*/, RTN_THROW));
+                    } else {
+                        Log.e(TAG, "Unknown IP address family for VPN endpoint: "
+                                + endpointAddress);
+                    }
+
+                    // Here is the last step and it must be done synchronously.
                     // Set the start time
                     mConfig.startTime = SystemClock.elapsedRealtime();
 
@@ -4773,25 +4767,26 @@
 
         try {
             // Build basic config
-            mConfig = new VpnConfig();
+            final VpnConfig config = new VpnConfig();
             if (VpnConfig.LEGACY_VPN.equals(packageName)) {
-                mConfig.legacy = true;
-                mConfig.session = profile.name;
-                mConfig.user = profile.key;
+                config.legacy = true;
+                config.session = profile.name;
+                config.user = profile.key;
 
                 // TODO: Add support for configuring meteredness via Settings. Until then, use a
                 // safe default.
-                mConfig.isMetered = true;
+                config.isMetered = true;
             } else {
-                mConfig.user = packageName;
-                mConfig.isMetered = profile.isMetered;
+                config.user = packageName;
+                config.isMetered = profile.isMetered;
             }
-            mConfig.startTime = SystemClock.elapsedRealtime();
-            mConfig.proxyInfo = profile.proxy;
-            mConfig.requiresInternetValidation = profile.requiresInternetValidation;
-            mConfig.excludeLocalRoutes = profile.excludeLocalRoutes;
-            mConfig.allowBypass = profile.isBypassable;
-            mConfig.disallowedApplications = getAppExclusionList(mPackage);
+            config.startTime = SystemClock.elapsedRealtime();
+            config.proxyInfo = profile.proxy;
+            config.requiresInternetValidation = profile.requiresInternetValidation;
+            config.excludeLocalRoutes = profile.excludeLocalRoutes;
+            config.allowBypass = profile.isBypassable;
+            config.disallowedApplications = getAppExclusionList(mPackage);
+            mConfig = config;
 
             switch (profile.type) {
                 case VpnProfile.TYPE_IKEV2_IPSEC_USER_PASS:
@@ -4805,6 +4800,7 @@
                     mVpnRunner.start();
                     break;
                 default:
+                    mConfig = null;
                     updateState(DetailedState.FAILED, "Invalid platform VPN type");
                     Log.d(TAG, "Unknown VPN profile type: " + profile.type);
                     break;
@@ -5216,7 +5212,7 @@
                 pw.println("MOBIKE " + (runner.mMobikeEnabled ? "enabled" : "disabled"));
                 pw.println("Profile: " + runner.mProfile);
                 pw.println("Token: " + runner.mCurrentToken);
-                if (mDataStallSuspected) pw.println("Data stall suspected");
+                pw.println("Validation failed retry count:" + runner.mValidationFailRetryCount);
                 if (runner.mScheduledHandleDataStallFuture != null) {
                     pw.println("Reset session scheduled");
                 }
diff --git a/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java b/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java
index eb7fa10..add94b1 100644
--- a/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java
+++ b/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java
@@ -172,6 +172,7 @@
     private DeviceState mRearDisplayState;
 
     // TODO(259328837) Generalize for all pending feature requests in the future
+    @GuardedBy("mLock")
     @Nullable
     private OverrideRequest mRearDisplayPendingOverrideRequest;
 
@@ -779,7 +780,7 @@
      * {@link StatusBarManagerInternal} to notify SystemUI to display the educational dialog.
      */
     @GuardedBy("mLock")
-    private void showRearDisplayEducationalOverlayLocked(OverrideRequest request) {
+    private void showRearDisplayEducationalOverlayLocked(@NonNull OverrideRequest request) {
         mRearDisplayPendingOverrideRequest = request;
 
         StatusBarManagerInternal statusBar =
@@ -844,8 +845,8 @@
      * request if it was dismissed in a way that should cancel the feature.
      */
     private void onStateRequestOverlayDismissedInternal(boolean shouldCancelRequest) {
-        if (mRearDisplayPendingOverrideRequest != null) {
-            synchronized (mLock) {
+        synchronized (mLock) {
+            if (mRearDisplayPendingOverrideRequest != null) {
                 if (shouldCancelRequest) {
                     ProcessRecord processRecord = mProcessRecords.get(
                             mRearDisplayPendingOverrideRequest.getPid());
diff --git a/services/core/java/com/android/server/display/BrightnessRangeController.java b/services/core/java/com/android/server/display/BrightnessRangeController.java
index 47cde15..5b11cfe 100644
--- a/services/core/java/com/android/server/display/BrightnessRangeController.java
+++ b/services/core/java/com/android/server/display/BrightnessRangeController.java
@@ -18,29 +18,42 @@
 
 import android.hardware.display.BrightnessInfo;
 import android.os.IBinder;
+import android.provider.DeviceConfigInterface;
+
+import com.android.server.display.feature.DeviceConfigParameterProvider;
 
 import java.io.PrintWriter;
 import java.util.function.BooleanSupplier;
 
 class BrightnessRangeController {
 
-    private static final boolean NBM_FEATURE_FLAG = false;
-
     private final HighBrightnessModeController mHbmController;
     private final NormalBrightnessModeController mNormalBrightnessModeController =
             new NormalBrightnessModeController();
 
     private final Runnable mModeChangeCallback;
+    private final boolean mUseNbmController;
+
 
     BrightnessRangeController(HighBrightnessModeController hbmController,
             Runnable modeChangeCallback) {
-        mHbmController = hbmController;
-        mModeChangeCallback = modeChangeCallback;
+        this(hbmController, modeChangeCallback,
+                new DeviceConfigParameterProvider(DeviceConfigInterface.REAL));
     }
 
+    BrightnessRangeController(HighBrightnessModeController hbmController,
+            Runnable modeChangeCallback, DeviceConfigParameterProvider configParameterProvider) {
+        mHbmController = hbmController;
+        mModeChangeCallback = modeChangeCallback;
+        mUseNbmController = configParameterProvider.isNormalBrightnessControllerFeatureEnabled();
+    }
 
     void dump(PrintWriter pw) {
+        pw.println("BrightnessRangeController:");
+        pw.println("  mUseNormalBrightnessController=" + mUseNbmController);
         mHbmController.dump(pw);
+        mNormalBrightnessModeController.dump(pw);
+
     }
 
     void onAmbientLuxChange(float ambientLux) {
@@ -90,7 +103,7 @@
 
 
     float getCurrentBrightnessMax() {
-        if (NBM_FEATURE_FLAG && mHbmController.getHighBrightnessMode()
+        if (mUseNbmController && mHbmController.getHighBrightnessMode()
                 == BrightnessInfo.HIGH_BRIGHTNESS_MODE_OFF) {
             return Math.min(mHbmController.getCurrentBrightnessMax(),
                     mNormalBrightnessModeController.getCurrentBrightnessMax());
@@ -111,7 +124,7 @@
     }
 
     private void applyChanges(BooleanSupplier nbmChangesFunc, Runnable hbmChangesFunc) {
-        if (NBM_FEATURE_FLAG) {
+        if (mUseNbmController) {
             boolean nbmTransitionChanged = nbmChangesFunc.getAsBoolean();
             hbmChangesFunc.run();
             // if nbm transition changed - trigger callback
diff --git a/services/core/java/com/android/server/display/BrightnessThrottler.java b/services/core/java/com/android/server/display/BrightnessThrottler.java
index cfdcd63..c421ec0 100644
--- a/services/core/java/com/android/server/display/BrightnessThrottler.java
+++ b/services/core/java/com/android/server/display/BrightnessThrottler.java
@@ -22,7 +22,6 @@
 import android.annotation.Nullable;
 import android.content.Context;
 import android.hardware.display.BrightnessInfo;
-import android.hardware.display.DisplayManager;
 import android.os.Handler;
 import android.os.HandlerExecutor;
 import android.os.IThermalEventListener;
@@ -38,6 +37,7 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.server.display.DisplayDeviceConfig.ThermalBrightnessThrottlingData;
 import com.android.server.display.DisplayDeviceConfig.ThermalBrightnessThrottlingData.ThrottlingLevel;
+import com.android.server.display.feature.DeviceConfigParameterProvider;
 
 import java.io.PrintWriter;
 import java.util.ArrayList;
@@ -63,7 +63,7 @@
     private final Runnable mThrottlingChangeCallback;
     private final SkinThermalStatusObserver mSkinThermalStatusObserver;
     private final DeviceConfigListener mDeviceConfigListener;
-    private final DeviceConfigInterface mDeviceConfig;
+    private final DeviceConfigParameterProvider mConfigParameterProvider;
 
     private int mThrottlingStatus;
 
@@ -118,7 +118,7 @@
         mSkinThermalStatusObserver = new SkinThermalStatusObserver(mInjector, mHandler);
 
         mUniqueDisplayId = uniqueDisplayId;
-        mDeviceConfig = injector.getDeviceConfig();
+        mConfigParameterProvider = new DeviceConfigParameterProvider(injector.getDeviceConfig());
         mDeviceConfigListener = new DeviceConfigListener();
         mThermalBrightnessThrottlingDataId = throttlingDataId;
         mDdcThermalThrottlingDataMap = thermalBrightnessThrottlingDataMap;
@@ -145,7 +145,7 @@
 
     void stop() {
         mSkinThermalStatusObserver.stopObserving();
-        mDeviceConfig.removeOnPropertiesChangedListener(mDeviceConfigListener);
+        mConfigParameterProvider.removeOnPropertiesChangedListener(mDeviceConfigListener);
         // We're asked to stop throttling, so reset brightness restrictions.
         mBrightnessCap = PowerManager.BRIGHTNESS_MAX;
         mBrightnessMaxReason = BrightnessInfo.BRIGHTNESS_MAX_REASON_NONE;
@@ -248,12 +248,6 @@
         mSkinThermalStatusObserver.dump(pw);
     }
 
-    private String getThermalBrightnessThrottlingDataString() {
-        return mDeviceConfig.getString(DeviceConfig.NAMESPACE_DISPLAY_MANAGER,
-                DisplayManager.DeviceConfig.KEY_BRIGHTNESS_THROTTLING_DATA,
-                /* defaultValue= */ null);
-    }
-
     // The brightness throttling data id may or may not be specified in the string that is passed
     // in, if there is none specified, we assume it is for the default case. Each string passed in
     // here must be for one display and one throttling id.
@@ -318,7 +312,8 @@
     private void loadThermalBrightnessThrottlingDataFromDeviceConfig() {
         HashMap<String, HashMap<String, ThermalBrightnessThrottlingData>> tempThrottlingData =
                 new HashMap<>(1);
-        mThermalBrightnessThrottlingDataString = getThermalBrightnessThrottlingDataString();
+        mThermalBrightnessThrottlingDataString =
+                mConfigParameterProvider.getBrightnessThrottlingData();
         boolean validConfig = true;
         mThermalBrightnessThrottlingDataOverride.clear();
         if (mThermalBrightnessThrottlingDataString != null) {
@@ -390,8 +385,7 @@
         public Executor mExecutor = new HandlerExecutor(mDeviceConfigHandler);
 
         public void startListening() {
-            mDeviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_DISPLAY_MANAGER,
-                    mExecutor, this);
+            mConfigParameterProvider.addOnPropertiesChangedListener(mExecutor, this);
         }
 
         @Override
diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java
index 858800a..dbe15b6 100644
--- a/services/core/java/com/android/server/display/DisplayManagerService.java
+++ b/services/core/java/com/android/server/display/DisplayManagerService.java
@@ -24,7 +24,6 @@
 import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_CACHED;
 import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_GONE;
 import static android.hardware.display.DisplayManager.EventsMask;
-import static android.hardware.display.DisplayManager.HDR_OUTPUT_CONTROL_FLAG;
 import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_ALWAYS_UNLOCKED;
 import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR;
 import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_CAN_SHOW_WITH_INSECURE_KEYGUARD;
@@ -42,7 +41,6 @@
 import static android.hardware.display.HdrConversionMode.HDR_CONVERSION_UNSUPPORTED;
 import static android.os.Process.FIRST_APPLICATION_UID;
 import static android.os.Process.ROOT_UID;
-import static android.provider.DeviceConfig.NAMESPACE_DISPLAY_MANAGER;
 
 import android.Manifest;
 import android.annotation.NonNull;
@@ -116,7 +114,7 @@
 import android.os.Trace;
 import android.os.UserHandle;
 import android.os.UserManager;
-import android.provider.DeviceConfig;
+import android.provider.DeviceConfigInterface;
 import android.provider.Settings;
 import android.text.TextUtils;
 import android.util.ArraySet;
@@ -152,6 +150,7 @@
 import com.android.server.UiThread;
 import com.android.server.companion.virtual.VirtualDeviceManagerInternal;
 import com.android.server.display.DisplayDeviceConfig.SensorData;
+import com.android.server.display.feature.DeviceConfigParameterProvider;
 import com.android.server.display.layout.Layout;
 import com.android.server.display.mode.DisplayModeDirector;
 import com.android.server.display.utils.SensorUtils;
@@ -506,6 +505,8 @@
 
     private final BrightnessSynchronizer mBrightnessSynchronizer;
 
+    private final DeviceConfigParameterProvider mConfigParameterProvider;
+
     /**
      * Applications use {@link android.view.Display#getRefreshRate} and
      * {@link android.view.Display.Mode#getRefreshRate} to know what is the display refresh rate.
@@ -558,6 +559,7 @@
         mWideColorSpace = colorSpaces[1];
         mOverlayProperties = SurfaceControl.getOverlaySupport();
         mSystemReady = false;
+        mConfigParameterProvider = new DeviceConfigParameterProvider(DeviceConfigInterface.REAL);
     }
 
     public void setupSchedulerPolicies() {
@@ -694,11 +696,11 @@
         synchronized (mSyncRoot) {
             mSafeMode = safeMode;
             mSystemReady = true;
-            mIsHdrOutputControlEnabled = isDeviceConfigHdrOutputControlEnabled();
-            DeviceConfig.addOnPropertiesChangedListener(NAMESPACE_DISPLAY_MANAGER,
-                    BackgroundThread.getExecutor(),
+            mIsHdrOutputControlEnabled =
+                    mConfigParameterProvider.isHdrOutputControlFeatureEnabled();
+            mConfigParameterProvider.addOnPropertiesChangedListener(BackgroundThread.getExecutor(),
                     properties -> mIsHdrOutputControlEnabled =
-                            isDeviceConfigHdrOutputControlEnabled());
+                            mConfigParameterProvider.isHdrOutputControlFeatureEnabled());
             // Just in case the top inset changed before the system was ready. At this point, any
             // relevant configuration should be in place.
             recordTopInsetLocked(mLogicalDisplayMapper.getDisplayLocked(Display.DEFAULT_DISPLAY));
@@ -729,12 +731,6 @@
         mContext.registerReceiver(mIdleModeReceiver, filter);
     }
 
-    private boolean isDeviceConfigHdrOutputControlEnabled() {
-        return DeviceConfig.getBoolean(NAMESPACE_DISPLAY_MANAGER,
-                HDR_OUTPUT_CONTROL_FLAG,
-                true);
-    }
-
     @VisibleForTesting
     Handler getDisplayHandler() {
         return mHandler;
@@ -3158,8 +3154,7 @@
                     + "display: " + display.getDisplayIdLocked());
             return null;
         }
-        if (DeviceConfig.getBoolean("display_manager",
-                "use_newly_structured_display_power_controller", true)) {
+        if (mConfigParameterProvider.isNewPowerControllerFeatureEnabled()) {
             displayPowerController = new DisplayPowerController2(
                     mContext, /* injector= */ null, mDisplayPowerCallbacks, mPowerHandler,
                     mSensorManager, mDisplayBlanker, display, mBrightnessTracker, brightnessSetting,
diff --git a/services/core/java/com/android/server/display/LocalDisplayAdapter.java b/services/core/java/com/android/server/display/LocalDisplayAdapter.java
index 7701bc6..89d865e 100644
--- a/services/core/java/com/android/server/display/LocalDisplayAdapter.java
+++ b/services/core/java/com/android/server/display/LocalDisplayAdapter.java
@@ -496,7 +496,7 @@
         private void loadDisplayDeviceConfig() {
             // Load display device config
             final Context context = getOverlayContext();
-            mDisplayDeviceConfig = DisplayDeviceConfig.create(context, mPhysicalDisplayId,
+            mDisplayDeviceConfig = mInjector.createDisplayDeviceConfig(context, mPhysicalDisplayId,
                     mIsFirstDisplay);
 
             // Load brightness HWC quirk
@@ -1336,6 +1336,11 @@
         public SurfaceControlProxy getSurfaceControlProxy() {
             return new SurfaceControlProxy();
         }
+
+        public DisplayDeviceConfig createDisplayDeviceConfig(Context context,
+                long physicalDisplayId, boolean isFirstDisplay) {
+            return DisplayDeviceConfig.create(context, physicalDisplayId, isFirstDisplay);
+        }
     }
 
     public interface DisplayEventListener {
diff --git a/services/core/java/com/android/server/display/NormalBrightnessModeController.java b/services/core/java/com/android/server/display/NormalBrightnessModeController.java
index dbabc24..135ebd8 100644
--- a/services/core/java/com/android/server/display/NormalBrightnessModeController.java
+++ b/services/core/java/com/android/server/display/NormalBrightnessModeController.java
@@ -21,6 +21,7 @@
 
 import com.android.server.display.DisplayDeviceConfig.BrightnessLimitMapType;
 
+import java.io.PrintWriter;
 import java.util.HashMap;
 import java.util.Map;
 
@@ -60,6 +61,14 @@
         return recalculateMaxBrightness();
     }
 
+    void dump(PrintWriter pw) {
+        pw.println("NormalBrightnessModeController:");
+        pw.println("  mAutoBrightnessEnabled=" + mAutoBrightnessEnabled);
+        pw.println("  mAmbientLux=" + mAmbientLux);
+        pw.println("  mMaxBrightness=" + mMaxBrightness);
+        pw.println("  mMaxBrightnessLimits=" + mMaxBrightnessLimits);
+    }
+
     private boolean recalculateMaxBrightness() {
         float foundAmbientBoundary = Float.MAX_VALUE;
         float foundMaxBrightness = PowerManager.BRIGHTNESS_MAX;
diff --git a/services/core/java/com/android/server/display/feature/DeviceConfigParameterProvider.java b/services/core/java/com/android/server/display/feature/DeviceConfigParameterProvider.java
new file mode 100644
index 0000000..feebdf1
--- /dev/null
+++ b/services/core/java/com/android/server/display/feature/DeviceConfigParameterProvider.java
@@ -0,0 +1,168 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.display.feature;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.hardware.display.DisplayManager;
+import android.provider.DeviceConfig;
+import android.provider.DeviceConfigInterface;
+import android.util.Slog;
+
+import java.util.concurrent.Executor;
+
+/**
+ * Helper class to access all DeviceConfig features for display_manager namespace
+ *
+ **/
+public class DeviceConfigParameterProvider {
+
+    private static final String TAG = "DisplayFeatureProvider";
+
+    private final DeviceConfigInterface mDeviceConfig;
+
+    public DeviceConfigParameterProvider(DeviceConfigInterface deviceConfig) {
+        mDeviceConfig = deviceConfig;
+    }
+
+    // feature: revamping_display_power_controller_feature
+    // parameter: use_newly_structured_display_power_controller
+    public boolean isNewPowerControllerFeatureEnabled() {
+        return mDeviceConfig.getBoolean(DeviceConfig.NAMESPACE_DISPLAY_MANAGER,
+                DisplayManager.DeviceConfig.KEY_NEW_POWER_CONTROLLER, true);
+    }
+
+    // feature: hdr_output_control
+    // parameter: enable_hdr_output_control
+    public boolean isHdrOutputControlFeatureEnabled() {
+        return DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_DISPLAY_MANAGER,
+                DisplayManager.HDR_OUTPUT_CONTROL_FLAG, true);
+    }
+
+    // feature: flexible_brightness_range_feature
+    // parameter: normal_brightness_mode_controller_enabled
+    public boolean isNormalBrightnessControllerFeatureEnabled() {
+        return DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_DISPLAY_MANAGER,
+                DisplayManager.DeviceConfig.KEY_USE_NORMAL_BRIGHTNESS_MODE_CONTROLLER, false);
+    }
+
+    // feature: smooth_display_feature
+    // parameter: peak_refresh_rate_default
+    public float getPeakRefreshRateDefault() {
+        return mDeviceConfig.getFloat(DeviceConfig.NAMESPACE_DISPLAY_MANAGER,
+                DisplayManager.DeviceConfig.KEY_PEAK_REFRESH_RATE_DEFAULT, -1);
+    }
+
+    // Test parameters
+    // usage e.g.: adb shell device_config put display_manager refresh_rate_in_hbm_sunlight 90
+
+    // allows to customize brightness throttling data
+    public String getBrightnessThrottlingData() {
+        return mDeviceConfig.getString(DeviceConfig.NAMESPACE_DISPLAY_MANAGER,
+                DisplayManager.DeviceConfig.KEY_BRIGHTNESS_THROTTLING_DATA, null);
+    }
+
+    public int getRefreshRateInHbmSunlight() {
+        return mDeviceConfig.getInt(DeviceConfig.NAMESPACE_DISPLAY_MANAGER,
+                DisplayManager.DeviceConfig.KEY_REFRESH_RATE_IN_HBM_SUNLIGHT, -1);
+    }
+
+    public int getRefreshRateInHbmHdr() {
+        return mDeviceConfig.getInt(DeviceConfig.NAMESPACE_DISPLAY_MANAGER,
+                DisplayManager.DeviceConfig.KEY_REFRESH_RATE_IN_HBM_HDR, -1);
+    }
+
+
+    public int getRefreshRateInHighZone() {
+        return mDeviceConfig.getInt(DeviceConfig.NAMESPACE_DISPLAY_MANAGER,
+                DisplayManager.DeviceConfig.KEY_REFRESH_RATE_IN_HIGH_ZONE, -1);
+    }
+
+    public int getRefreshRateInLowZone() {
+        return mDeviceConfig.getInt(DeviceConfig.NAMESPACE_DISPLAY_MANAGER,
+                DisplayManager.DeviceConfig.KEY_REFRESH_RATE_IN_LOW_ZONE, -1);
+    }
+
+    /** Return null if no such property or wrong format (not comma separated integers). */
+    @Nullable
+    public int[] getHighAmbientBrightnessThresholds() {
+        return getIntArrayProperty(DisplayManager.DeviceConfig
+                .KEY_FIXED_REFRESH_RATE_HIGH_AMBIENT_BRIGHTNESS_THRESHOLDS);
+    }
+
+    /** Return null if no such property or wrong format (not comma separated integers). */
+    @Nullable
+    public int[] getHighDisplayBrightnessThresholds() {
+        return getIntArrayProperty(DisplayManager.DeviceConfig
+                .KEY_FIXED_REFRESH_RATE_HIGH_DISPLAY_BRIGHTNESS_THRESHOLDS);
+    }
+
+    /** Return null if no such property or wrong format (not comma separated integers). */
+    @Nullable
+    public int[] getLowDisplayBrightnessThresholds() {
+        return getIntArrayProperty(DisplayManager.DeviceConfig
+                .KEY_FIXED_REFRESH_RATE_LOW_DISPLAY_BRIGHTNESS_THRESHOLDS);
+    }
+
+    /** Return null if no such property or wrong format (not comma separated integers). */
+    @Nullable
+    public int[] getLowAmbientBrightnessThresholds() {
+        return getIntArrayProperty(DisplayManager.DeviceConfig
+                .KEY_FIXED_REFRESH_RATE_LOW_AMBIENT_BRIGHTNESS_THRESHOLDS);
+    }
+
+    /** add property change listener to DeviceConfig */
+    public void addOnPropertiesChangedListener(Executor executor,
+            DeviceConfig.OnPropertiesChangedListener listener) {
+        mDeviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_DISPLAY_MANAGER,
+                executor, listener);
+    }
+
+    /** remove property change listener from DeviceConfig */
+    public void removeOnPropertiesChangedListener(
+            DeviceConfig.OnPropertiesChangedListener listener) {
+        mDeviceConfig.removeOnPropertiesChangedListener(listener);
+    }
+
+    @Nullable
+    private int[] getIntArrayProperty(String prop) {
+        String strArray = mDeviceConfig.getString(DeviceConfig.NAMESPACE_DISPLAY_MANAGER, prop,
+                null);
+
+        if (strArray != null) {
+            return parseIntArray(strArray);
+        }
+        return null;
+    }
+
+    @Nullable
+    private int[] parseIntArray(@NonNull String strArray) {
+        String[] items = strArray.split(",");
+        int[] array = new int[items.length];
+
+        try {
+            for (int i = 0; i < array.length; i++) {
+                array[i] = Integer.parseInt(items[i]);
+            }
+        } catch (NumberFormatException e) {
+            Slog.e(TAG, "Incorrect format for array: '" + strArray + "'", e);
+            array = null;
+        }
+
+        return array;
+    }
+}
diff --git a/services/core/java/com/android/server/display/mode/DisplayModeDirector.java b/services/core/java/com/android/server/display/mode/DisplayModeDirector.java
index 1889578..11e35ce 100644
--- a/services/core/java/com/android/server/display/mode/DisplayModeDirector.java
+++ b/services/core/java/com/android/server/display/mode/DisplayModeDirector.java
@@ -20,6 +20,7 @@
 import static android.hardware.display.DisplayManagerInternal.REFRESH_RATE_LIMIT_HIGH_BRIGHTNESS_MODE;
 import static android.os.PowerManager.BRIGHTNESS_INVALID;
 
+import android.annotation.IntegerRes;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.content.ContentResolver;
@@ -68,6 +69,7 @@
 import com.android.internal.os.BackgroundThread;
 import com.android.server.LocalServices;
 import com.android.server.display.DisplayDeviceConfig;
+import com.android.server.display.feature.DeviceConfigParameterProvider;
 import com.android.server.display.utils.AmbientFilter;
 import com.android.server.display.utils.AmbientFilterFactory;
 import com.android.server.display.utils.SensorUtils;
@@ -84,6 +86,7 @@
 import java.util.Locale;
 import java.util.Objects;
 import java.util.concurrent.Callable;
+import java.util.function.IntSupplier;
 
 /**
  * The DisplayModeDirector is responsible for determining what modes are allowed to be automatically
@@ -117,7 +120,7 @@
     private final SensorObserver mSensorObserver;
     private final HbmObserver mHbmObserver;
     private final SkinThermalStatusObserver mSkinThermalStatusObserver;
-    private final DeviceConfigInterface mDeviceConfig;
+    private final DeviceConfigParameterProvider mConfigParameterProvider;
     private final DeviceConfigDisplaySettings mDeviceConfigDisplaySettings;
 
     @GuardedBy("mLock")
@@ -157,7 +160,7 @@
         mSupportedModesByDisplay = new SparseArray<>();
         mDefaultModeByDisplay = new SparseArray<>();
         mAppRequestObserver = new AppRequestObserver();
-        mDeviceConfig = injector.getDeviceConfig();
+        mConfigParameterProvider = new DeviceConfigParameterProvider(injector.getDeviceConfig());
         mDeviceConfigDisplaySettings = new DeviceConfigDisplaySettings();
         mSettingsObserver = new SettingsObserver(context, handler);
         mBrightnessObserver = new BrightnessObserver(context, handler, injector);
@@ -681,9 +684,9 @@
         synchronized (mLock) {
             mDefaultDisplayDeviceConfig = displayDeviceConfig;
             mSettingsObserver.setRefreshRates(displayDeviceConfig,
-                /* attemptLoadingFromDeviceConfig= */ true);
+                /* attemptReadFromFeatureParams= */ true);
             mBrightnessObserver.updateBlockingZoneThresholds(displayDeviceConfig,
-                /* attemptLoadingFromDeviceConfig= */ true);
+                /* attemptReadFromFeatureParams= */ true);
             mBrightnessObserver.reloadLightSensor(displayDeviceConfig);
             mHbmObserver.setupHdrRefreshRates(displayDeviceConfig);
         }
@@ -1087,7 +1090,7 @@
             // reading from the DeviceConfig is an intensive IO operation and having it in the
             // startup phase where we thrive to keep the latency very low has significant impact.
             setRefreshRates(/* displayDeviceConfig= */ null,
-                /* attemptLoadingFromDeviceConfig= */ false);
+                /* attemptReadFromFeatureParams= */ false);
         }
 
         /**
@@ -1095,8 +1098,8 @@
          * if missing from DisplayDeviceConfig, and finally fallback to config.xml.
          */
         public void setRefreshRates(DisplayDeviceConfig displayDeviceConfig,
-                boolean attemptLoadingFromDeviceConfig) {
-            setDefaultPeakRefreshRate(displayDeviceConfig, attemptLoadingFromDeviceConfig);
+                boolean attemptReadFromFeatureParams) {
+            setDefaultPeakRefreshRate(displayDeviceConfig, attemptReadFromFeatureParams);
             mDefaultRefreshRate =
                     (displayDeviceConfig == null) ? (float) mContext.getResources().getInteger(
                         R.integer.config_defaultRefreshRate)
@@ -1113,9 +1116,9 @@
             cr.registerContentObserver(mMatchContentFrameRateSetting, false /*notifyDescendants*/,
                     this);
 
-            Float deviceConfigDefaultPeakRefresh =
-                    mDeviceConfigDisplaySettings.getDefaultPeakRefreshRate();
-            if (deviceConfigDefaultPeakRefresh != null) {
+            float deviceConfigDefaultPeakRefresh =
+                    mConfigParameterProvider.getPeakRefreshRateDefault();
+            if (deviceConfigDefaultPeakRefresh != -1) {
                 mDefaultPeakRefreshRate = deviceConfigDefaultPeakRefresh;
             }
 
@@ -1137,7 +1140,7 @@
             synchronized (mLock) {
                 if (defaultPeakRefreshRate == null) {
                     setDefaultPeakRefreshRate(mDefaultDisplayDeviceConfig,
-                            /* attemptLoadingFromDeviceConfig= */ false);
+                            /* attemptReadFromFeatureParams= */ false);
                     updateRefreshRateSettingLocked();
                 } else if (mDefaultPeakRefreshRate != defaultPeakRefreshRate) {
                     mDefaultPeakRefreshRate = defaultPeakRefreshRate;
@@ -1171,18 +1174,17 @@
         }
 
         private void setDefaultPeakRefreshRate(DisplayDeviceConfig displayDeviceConfig,
-                boolean attemptLoadingFromDeviceConfig) {
-            Float defaultPeakRefreshRate = null;
+                boolean attemptReadFromFeatureParams) {
+            float defaultPeakRefreshRate = -1;
 
-            if (attemptLoadingFromDeviceConfig) {
+            if (attemptReadFromFeatureParams) {
                 try {
-                    defaultPeakRefreshRate =
-                        mDeviceConfigDisplaySettings.getDefaultPeakRefreshRate();
+                    defaultPeakRefreshRate = mConfigParameterProvider.getPeakRefreshRateDefault();
                 } catch (Exception exception) {
                     // Do nothing
                 }
             }
-            if (defaultPeakRefreshRate == null) {
+            if (defaultPeakRefreshRate == -1) {
                 defaultPeakRefreshRate =
                         (displayDeviceConfig == null) ? (float) mContext.getResources().getInteger(
                                 R.integer.config_defaultPeakRefreshRate)
@@ -1528,7 +1530,7 @@
             mHandler = handler;
             mInjector = injector;
             updateBlockingZoneThresholds(/* displayDeviceConfig= */ null,
-                /* attemptLoadingFromDeviceConfig= */ false);
+                /* attemptReadFromFeatureParams= */ false);
             mRefreshRateInHighZone = context.getResources().getInteger(
                     R.integer.config_fixedRefreshRateInHighZone);
         }
@@ -1537,10 +1539,10 @@
          * This is used to update the blocking zone thresholds from the DeviceConfig, which
          * if missing from DisplayDeviceConfig, and finally fallback to config.xml.
          */
-        public void updateBlockingZoneThresholds(DisplayDeviceConfig displayDeviceConfig,
-                boolean attemptLoadingFromDeviceConfig) {
-            loadLowBrightnessThresholds(displayDeviceConfig, attemptLoadingFromDeviceConfig);
-            loadHighBrightnessThresholds(displayDeviceConfig, attemptLoadingFromDeviceConfig);
+        public void updateBlockingZoneThresholds(@Nullable DisplayDeviceConfig displayDeviceConfig,
+                boolean attemptReadFromFeatureParams) {
+            loadLowBrightnessThresholds(displayDeviceConfig, attemptReadFromFeatureParams);
+            loadHighBrightnessThresholds(displayDeviceConfig, attemptReadFromFeatureParams);
         }
 
         @VisibleForTesting
@@ -1579,20 +1581,20 @@
             return mRefreshRateInLowZone;
         }
 
-        private void loadLowBrightnessThresholds(DisplayDeviceConfig displayDeviceConfig,
-                boolean attemptLoadingFromDeviceConfig) {
-            loadRefreshRateInHighZone(displayDeviceConfig, attemptLoadingFromDeviceConfig);
-            loadRefreshRateInLowZone(displayDeviceConfig, attemptLoadingFromDeviceConfig);
+        private void loadLowBrightnessThresholds(@Nullable DisplayDeviceConfig displayDeviceConfig,
+                boolean attemptReadFromFeatureParams) {
+            loadRefreshRateInHighZone(displayDeviceConfig, attemptReadFromFeatureParams);
+            loadRefreshRateInLowZone(displayDeviceConfig, attemptReadFromFeatureParams);
             mLowDisplayBrightnessThresholds = loadBrightnessThresholds(
-                    () -> mDeviceConfigDisplaySettings.getLowDisplayBrightnessThresholds(),
+                    () -> mConfigParameterProvider.getLowDisplayBrightnessThresholds(),
                     () -> displayDeviceConfig.getLowDisplayBrightnessThresholds(),
                     R.array.config_brightnessThresholdsOfPeakRefreshRate,
-                    displayDeviceConfig, attemptLoadingFromDeviceConfig);
+                    displayDeviceConfig, attemptReadFromFeatureParams);
             mLowAmbientBrightnessThresholds = loadBrightnessThresholds(
-                    () -> mDeviceConfigDisplaySettings.getLowAmbientBrightnessThresholds(),
+                    () -> mConfigParameterProvider.getLowAmbientBrightnessThresholds(),
                     () -> displayDeviceConfig.getLowAmbientBrightnessThresholds(),
                     R.array.config_ambientThresholdsOfPeakRefreshRate,
-                    displayDeviceConfig, attemptLoadingFromDeviceConfig);
+                    displayDeviceConfig, attemptReadFromFeatureParams);
             if (mLowDisplayBrightnessThresholds.length != mLowAmbientBrightnessThresholds.length) {
                 throw new RuntimeException("display low brightness threshold array and ambient "
                         + "brightness threshold array have different length: "
@@ -1604,55 +1606,55 @@
         }
 
         private void loadRefreshRateInLowZone(DisplayDeviceConfig displayDeviceConfig,
-                boolean attemptLoadingFromDeviceConfig) {
-            int refreshRateInLowZone =
-                    (displayDeviceConfig == null) ? mContext.getResources().getInteger(
-                        R.integer.config_defaultRefreshRateInZone)
-                        : displayDeviceConfig.getDefaultLowBlockingZoneRefreshRate();
-            if (attemptLoadingFromDeviceConfig) {
+                boolean attemptReadFromFeatureParams) {
+            int refreshRateInLowZone = -1;
+            if (attemptReadFromFeatureParams) {
                 try {
-                    refreshRateInLowZone = mDeviceConfig.getInt(
-                        DeviceConfig.NAMESPACE_DISPLAY_MANAGER,
-                        DisplayManager.DeviceConfig.KEY_REFRESH_RATE_IN_LOW_ZONE,
-                        refreshRateInLowZone);
+                    refreshRateInLowZone = mConfigParameterProvider.getRefreshRateInLowZone();
                 } catch (Exception exception) {
                     // Do nothing
                 }
             }
+            if (refreshRateInLowZone == -1) {
+                refreshRateInLowZone = (displayDeviceConfig == null)
+                        ? mContext.getResources().getInteger(
+                                R.integer.config_defaultRefreshRateInZone)
+                        : displayDeviceConfig.getDefaultLowBlockingZoneRefreshRate();
+            }
             mRefreshRateInLowZone = refreshRateInLowZone;
         }
 
         private void loadRefreshRateInHighZone(DisplayDeviceConfig displayDeviceConfig,
-                boolean attemptLoadingFromDeviceConfig) {
-            int refreshRateInHighZone =
-                    (displayDeviceConfig == null) ? mContext.getResources().getInteger(
-                        R.integer.config_fixedRefreshRateInHighZone) : displayDeviceConfig
-                        .getDefaultHighBlockingZoneRefreshRate();
-            if (attemptLoadingFromDeviceConfig) {
+                boolean attemptReadFromFeatureParams) {
+            int refreshRateInHighZone = -1;
+            if (attemptReadFromFeatureParams) {
                 try {
-                    refreshRateInHighZone = mDeviceConfig.getInt(
-                        DeviceConfig.NAMESPACE_DISPLAY_MANAGER,
-                        DisplayManager.DeviceConfig.KEY_REFRESH_RATE_IN_HIGH_ZONE,
-                        refreshRateInHighZone);
+                    refreshRateInHighZone = mConfigParameterProvider.getRefreshRateInHighZone();
                 } catch (Exception exception) {
                     // Do nothing
                 }
             }
+            if (refreshRateInHighZone == -1) {
+                refreshRateInHighZone = (displayDeviceConfig == null)
+                        ? mContext.getResources().getInteger(
+                                R.integer.config_fixedRefreshRateInHighZone)
+                        : displayDeviceConfig.getDefaultHighBlockingZoneRefreshRate();
+            }
             mRefreshRateInHighZone = refreshRateInHighZone;
         }
 
         private void loadHighBrightnessThresholds(DisplayDeviceConfig displayDeviceConfig,
-                boolean attemptLoadingFromDeviceConfig) {
+                boolean attemptReadFromFeatureParams) {
             mHighDisplayBrightnessThresholds = loadBrightnessThresholds(
-                    () -> mDeviceConfigDisplaySettings.getHighDisplayBrightnessThresholds(),
+                    () -> mConfigParameterProvider.getHighDisplayBrightnessThresholds(),
                     () -> displayDeviceConfig.getHighDisplayBrightnessThresholds(),
                     R.array.config_highDisplayBrightnessThresholdsOfFixedRefreshRate,
-                    displayDeviceConfig, attemptLoadingFromDeviceConfig);
+                    displayDeviceConfig, attemptReadFromFeatureParams);
             mHighAmbientBrightnessThresholds = loadBrightnessThresholds(
-                    () -> mDeviceConfigDisplaySettings.getHighAmbientBrightnessThresholds(),
+                    () -> mConfigParameterProvider.getHighAmbientBrightnessThresholds(),
                     () -> displayDeviceConfig.getHighAmbientBrightnessThresholds(),
                     R.array.config_highAmbientBrightnessThresholdsOfFixedRefreshRate,
-                    displayDeviceConfig, attemptLoadingFromDeviceConfig);
+                    displayDeviceConfig, attemptReadFromFeatureParams);
             if (mHighDisplayBrightnessThresholds.length
                     != mHighAmbientBrightnessThresholds.length) {
                 throw new RuntimeException("display high brightness threshold array and ambient "
@@ -1668,10 +1670,10 @@
                 Callable<int[]> loadFromDeviceConfigDisplaySettingsCallable,
                 Callable<int[]> loadFromDisplayDeviceConfigCallable,
                 int brightnessThresholdOfFixedRefreshRateKey,
-                DisplayDeviceConfig displayDeviceConfig, boolean attemptLoadingFromDeviceConfig) {
+                DisplayDeviceConfig displayDeviceConfig, boolean attemptReadFromFeatureParams) {
             int[] brightnessThresholds = null;
 
-            if (attemptLoadingFromDeviceConfig) {
+            if (attemptReadFromFeatureParams) {
                 try {
                     brightnessThresholds =
                         loadFromDeviceConfigDisplaySettingsCallable.call();
@@ -1715,9 +1717,9 @@
 
             // DeviceConfig is accessible after system ready.
             int[] lowDisplayBrightnessThresholds =
-                    mDeviceConfigDisplaySettings.getLowDisplayBrightnessThresholds();
+                    mConfigParameterProvider.getLowDisplayBrightnessThresholds();
             int[] lowAmbientBrightnessThresholds =
-                    mDeviceConfigDisplaySettings.getLowAmbientBrightnessThresholds();
+                    mConfigParameterProvider.getLowAmbientBrightnessThresholds();
 
             if (lowDisplayBrightnessThresholds != null && lowAmbientBrightnessThresholds != null
                     && lowDisplayBrightnessThresholds.length
@@ -1727,9 +1729,9 @@
             }
 
             int[] highDisplayBrightnessThresholds =
-                    mDeviceConfigDisplaySettings.getHighDisplayBrightnessThresholds();
+                    mConfigParameterProvider.getHighDisplayBrightnessThresholds();
             int[] highAmbientBrightnessThresholds =
-                    mDeviceConfigDisplaySettings.getHighAmbientBrightnessThresholds();
+                    mConfigParameterProvider.getHighAmbientBrightnessThresholds();
 
             if (highDisplayBrightnessThresholds != null && highAmbientBrightnessThresholds != null
                     && highDisplayBrightnessThresholds.length
@@ -1738,14 +1740,12 @@
                 mHighAmbientBrightnessThresholds = highAmbientBrightnessThresholds;
             }
 
-            final int refreshRateInLowZone = mDeviceConfigDisplaySettings
-                    .getRefreshRateInLowZone();
+            final int refreshRateInLowZone = mConfigParameterProvider.getRefreshRateInLowZone();
             if (refreshRateInLowZone != -1) {
                 mRefreshRateInLowZone = refreshRateInLowZone;
             }
 
-            final int refreshRateInHighZone = mDeviceConfigDisplaySettings
-                    .getRefreshRateInHighZone();
+            final int refreshRateInHighZone = mConfigParameterProvider.getRefreshRateInHighZone();
             if (refreshRateInHighZone != -1) {
                 mRefreshRateInHighZone = refreshRateInHighZone;
             }
@@ -1799,15 +1799,15 @@
                     displayDeviceConfig = mDefaultDisplayDeviceConfig;
                 }
                 mLowDisplayBrightnessThresholds = loadBrightnessThresholds(
-                        () -> mDeviceConfigDisplaySettings.getLowDisplayBrightnessThresholds(),
+                        () -> mConfigParameterProvider.getLowDisplayBrightnessThresholds(),
                         () -> displayDeviceConfig.getLowDisplayBrightnessThresholds(),
                         R.array.config_brightnessThresholdsOfPeakRefreshRate,
-                        displayDeviceConfig, /* attemptLoadingFromDeviceConfig= */ false);
+                        displayDeviceConfig, /* attemptReadFromFeatureParams= */ false);
                 mLowAmbientBrightnessThresholds = loadBrightnessThresholds(
-                        () -> mDeviceConfigDisplaySettings.getLowAmbientBrightnessThresholds(),
+                        () -> mConfigParameterProvider.getLowAmbientBrightnessThresholds(),
                         () -> displayDeviceConfig.getLowAmbientBrightnessThresholds(),
                         R.array.config_ambientThresholdsOfPeakRefreshRate,
-                        displayDeviceConfig, /* attemptLoadingFromDeviceConfig= */ false);
+                        displayDeviceConfig, /* attemptReadFromFeatureParams= */ false);
             }
             restartObserver();
         }
@@ -1822,7 +1822,7 @@
                 // from there.
                 synchronized (mLock) {
                     loadRefreshRateInLowZone(mDefaultDisplayDeviceConfig,
-                            /* attemptLoadingFromDeviceConfig= */ false);
+                            /* attemptReadFromFeatureParams= */ false);
                 }
                 restartObserver();
             } else if (refreshRate != mRefreshRateInLowZone) {
@@ -1843,15 +1843,15 @@
                     displayDeviceConfig = mDefaultDisplayDeviceConfig;
                 }
                 mHighDisplayBrightnessThresholds = loadBrightnessThresholds(
-                        () -> mDeviceConfigDisplaySettings.getHighDisplayBrightnessThresholds(),
+                        () -> mConfigParameterProvider.getLowDisplayBrightnessThresholds(),
                         () -> displayDeviceConfig.getHighDisplayBrightnessThresholds(),
                         R.array.config_highDisplayBrightnessThresholdsOfFixedRefreshRate,
-                        displayDeviceConfig, /* attemptLoadingFromDeviceConfig= */ false);
+                        displayDeviceConfig, /* attemptReadFromFeatureParams= */ false);
                 mHighAmbientBrightnessThresholds = loadBrightnessThresholds(
-                        () -> mDeviceConfigDisplaySettings.getHighAmbientBrightnessThresholds(),
+                        () -> mConfigParameterProvider.getHighAmbientBrightnessThresholds(),
                         () -> displayDeviceConfig.getHighAmbientBrightnessThresholds(),
                         R.array.config_highAmbientBrightnessThresholdsOfFixedRefreshRate,
-                        displayDeviceConfig, /* attemptLoadingFromDeviceConfig= */ false);
+                        displayDeviceConfig, /* attemptReadFromFeatureParams= */ false);
             }
             restartObserver();
         }
@@ -1866,7 +1866,7 @@
                 // from there.
                 synchronized (mLock) {
                     loadRefreshRateInHighZone(mDefaultDisplayDeviceConfig,
-                            /* attemptLoadingFromDeviceConfig= */ false);
+                            /* attemptReadFromFeatureParams= */ false);
                 }
                 restartObserver();
             } else if (refreshRate != mRefreshRateInHighZone) {
@@ -2675,113 +2675,55 @@
 
     private class DeviceConfigDisplaySettings implements DeviceConfig.OnPropertiesChangedListener {
         public void startListening() {
-            mDeviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_DISPLAY_MANAGER,
+            mConfigParameterProvider.addOnPropertiesChangedListener(
                     BackgroundThread.getExecutor(), this);
         }
 
-        /*
-         * Return null if no such property or wrong format (not comma separated integers).
-         */
-        public int[] getLowDisplayBrightnessThresholds() {
-            return getIntArrayProperty(
-                    DisplayManager.DeviceConfig
-                            .KEY_FIXED_REFRESH_RATE_LOW_DISPLAY_BRIGHTNESS_THRESHOLDS);
+        private int getRefreshRateInHbmHdr(DisplayDeviceConfig displayDeviceConfig) {
+            return getRefreshRate(
+                    () -> mConfigParameterProvider.getRefreshRateInHbmHdr(),
+                    () -> displayDeviceConfig.getDefaultRefreshRateInHbmHdr(),
+                    R.integer.config_defaultRefreshRateInHbmHdr,
+                    displayDeviceConfig
+            );
         }
 
-        /*
-         * Return null if no such property or wrong format (not comma separated integers).
-         */
-        public int[] getLowAmbientBrightnessThresholds() {
-            return getIntArrayProperty(
-                    DisplayManager.DeviceConfig
-                            .KEY_FIXED_REFRESH_RATE_LOW_AMBIENT_BRIGHTNESS_THRESHOLDS);
+        private int getRefreshRateInHbmSunlight(DisplayDeviceConfig displayDeviceConfig) {
+            return getRefreshRate(
+                    () -> mConfigParameterProvider.getRefreshRateInHbmSunlight(),
+                    () -> displayDeviceConfig.getDefaultRefreshRateInHbmSunlight(),
+                    R.integer.config_defaultRefreshRateInHbmSunlight,
+                    displayDeviceConfig
+            );
         }
 
-        public int getRefreshRateInLowZone() {
-            return mDeviceConfig.getInt(
-                    DeviceConfig.NAMESPACE_DISPLAY_MANAGER,
-                    DisplayManager.DeviceConfig.KEY_REFRESH_RATE_IN_LOW_ZONE, -1);
-
-        }
-
-        /*
-         * Return null if no such property or wrong format (not comma separated integers).
-         */
-        public int[] getHighDisplayBrightnessThresholds() {
-            return getIntArrayProperty(
-                    DisplayManager.DeviceConfig
-                            .KEY_FIXED_REFRESH_RATE_HIGH_DISPLAY_BRIGHTNESS_THRESHOLDS);
-        }
-
-        /*
-         * Return null if no such property or wrong format (not comma separated integers).
-         */
-        public int[] getHighAmbientBrightnessThresholds() {
-            return getIntArrayProperty(
-                    DisplayManager.DeviceConfig
-                            .KEY_FIXED_REFRESH_RATE_HIGH_AMBIENT_BRIGHTNESS_THRESHOLDS);
-        }
-
-        public int getRefreshRateInHighZone() {
-            return mDeviceConfig.getInt(
-                    DeviceConfig.NAMESPACE_DISPLAY_MANAGER,
-                    DisplayManager.DeviceConfig.KEY_REFRESH_RATE_IN_HIGH_ZONE,
-                    -1);
-        }
-
-        public int getRefreshRateInHbmHdr(DisplayDeviceConfig displayDeviceConfig) {
-            int refreshRate =
-                    (displayDeviceConfig == null) ? mContext.getResources().getInteger(
-                            R.integer.config_defaultRefreshRateInHbmHdr)
-                            : displayDeviceConfig.getDefaultRefreshRateInHbmHdr();
+        private int getRefreshRate(IntSupplier fromConfigPram, IntSupplier fromDisplayDeviceConfig,
+                @IntegerRes int configKey, DisplayDeviceConfig displayDeviceConfig) {
+            int refreshRate = -1;
             try {
-                refreshRate = mDeviceConfig.getInt(DeviceConfig.NAMESPACE_DISPLAY_MANAGER,
-                    DisplayManager.DeviceConfig.KEY_REFRESH_RATE_IN_HBM_HDR,
-                    refreshRate);
-            } catch (NullPointerException e) {
+                refreshRate = fromConfigPram.getAsInt();
+            } catch (NullPointerException npe) {
                 // Do Nothing
             }
-            return refreshRate;
-        }
-
-        public int getRefreshRateInHbmSunlight(DisplayDeviceConfig displayDeviceConfig) {
-            int refreshRate =
-                    (displayDeviceConfig == null) ? mContext.getResources()
-                        .getInteger(R.integer.config_defaultRefreshRateInHbmSunlight)
-                        : displayDeviceConfig.getDefaultRefreshRateInHbmSunlight();
-            try {
-                refreshRate = mDeviceConfig.getInt(DeviceConfig.NAMESPACE_DISPLAY_MANAGER,
-                    DisplayManager.DeviceConfig.KEY_REFRESH_RATE_IN_HBM_SUNLIGHT,
-                    refreshRate);
-            } catch (NullPointerException e) {
-                // Do Nothing
+            if (refreshRate == -1) {
+                refreshRate = (displayDeviceConfig == null)
+                                ? mContext.getResources().getInteger(configKey)
+                                : fromDisplayDeviceConfig.getAsInt();
             }
             return refreshRate;
         }
 
-        /*
-         * Return null if no such property
-         */
-        public Float getDefaultPeakRefreshRate() {
-            float defaultPeakRefreshRate = mDeviceConfig.getFloat(
-                    DeviceConfig.NAMESPACE_DISPLAY_MANAGER,
-                    DisplayManager.DeviceConfig.KEY_PEAK_REFRESH_RATE_DEFAULT, -1);
-
-            if (defaultPeakRefreshRate == -1) {
-                return null;
-            }
-            return defaultPeakRefreshRate;
-        }
-
         @Override
         public void onPropertiesChanged(@NonNull DeviceConfig.Properties properties) {
-            Float defaultPeakRefreshRate = getDefaultPeakRefreshRate();
+            float defaultPeakRefreshRate = mConfigParameterProvider.getPeakRefreshRateDefault();
             mHandler.obtainMessage(MSG_DEFAULT_PEAK_REFRESH_RATE_CHANGED,
-                    defaultPeakRefreshRate).sendToTarget();
+                    defaultPeakRefreshRate == -1 ? null : defaultPeakRefreshRate).sendToTarget();
 
-            int[] lowDisplayBrightnessThresholds = getLowDisplayBrightnessThresholds();
-            int[] lowAmbientBrightnessThresholds = getLowAmbientBrightnessThresholds();
-            final int refreshRateInLowZone = getRefreshRateInLowZone();
+            int[] lowDisplayBrightnessThresholds =
+                    mConfigParameterProvider.getLowDisplayBrightnessThresholds();
+            int[] lowAmbientBrightnessThresholds =
+                    mConfigParameterProvider.getLowAmbientBrightnessThresholds();
+            final int refreshRateInLowZone = mConfigParameterProvider.getRefreshRateInLowZone();
 
             mHandler.obtainMessage(MSG_LOW_BRIGHTNESS_THRESHOLDS_CHANGED,
                     new Pair<>(lowDisplayBrightnessThresholds, lowAmbientBrightnessThresholds))
@@ -2790,9 +2732,11 @@
             mHandler.obtainMessage(MSG_REFRESH_RATE_IN_LOW_ZONE_CHANGED, refreshRateInLowZone,
                     0).sendToTarget();
 
-            int[] highDisplayBrightnessThresholds = getHighDisplayBrightnessThresholds();
-            int[] highAmbientBrightnessThresholds = getHighAmbientBrightnessThresholds();
-            final int refreshRateInHighZone = getRefreshRateInHighZone();
+            int[] highDisplayBrightnessThresholds =
+                    mConfigParameterProvider.getHighDisplayBrightnessThresholds();
+            int[] highAmbientBrightnessThresholds =
+                    mConfigParameterProvider.getHighAmbientBrightnessThresholds();
+            final int refreshRateInHighZone = mConfigParameterProvider.getRefreshRateInHighZone();
 
             mHandler.obtainMessage(MSG_HIGH_BRIGHTNESS_THRESHOLDS_CHANGED,
                     new Pair<>(highDisplayBrightnessThresholds, highAmbientBrightnessThresholds))
@@ -2814,33 +2758,6 @@
                     .sendToTarget();
             }
         }
-
-        private int[] getIntArrayProperty(String prop) {
-            String strArray = mDeviceConfig.getString(DeviceConfig.NAMESPACE_DISPLAY_MANAGER, prop,
-                    null);
-
-            if (strArray != null) {
-                return parseIntArray(strArray);
-            }
-
-            return null;
-        }
-
-        private int[] parseIntArray(@NonNull String strArray) {
-            String[] items = strArray.split(",");
-            int[] array = new int[items.length];
-
-            try {
-                for (int i = 0; i < array.length; i++) {
-                    array[i] = Integer.parseInt(items[i]);
-                }
-            } catch (NumberFormatException e) {
-                Slog.e(TAG, "Incorrect format for array: '" + strArray + "'", e);
-                array = null;
-            }
-
-            return array;
-        }
     }
 
     interface Injector {
diff --git a/services/core/java/com/android/server/dreams/DreamController.java b/services/core/java/com/android/server/dreams/DreamController.java
index 6d70d21..633bf731 100644
--- a/services/core/java/com/android/server/dreams/DreamController.java
+++ b/services/core/java/com/android/server/dreams/DreamController.java
@@ -16,11 +16,11 @@
 
 package com.android.server.dreams;
 
-import static android.app.WindowConfiguration.ACTIVITY_TYPE_DREAM;
 import static android.content.Intent.FLAG_RECEIVER_FOREGROUND;
 
 import android.app.ActivityTaskManager;
 import android.app.BroadcastOptions;
+import android.app.IAppTask;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
@@ -214,6 +214,27 @@
     }
 
     /**
+     * Provides an appTask for the dream with token {@code dreamToken}, so that the dream controller
+     * can stop the dream task when necessary.
+     */
+    void setDreamAppTask(Binder dreamToken, IAppTask appTask) {
+        if (mCurrentDream == null || mCurrentDream.mToken != dreamToken
+                || mCurrentDream.mAppTask != null) {
+            Slog.e(TAG, "Illegal dream activity start. mCurrentDream.mToken = "
+                    + mCurrentDream.mToken + ", illegal dreamToken = " + dreamToken
+                    + ". Ending this dream activity.");
+            try {
+                appTask.finishAndRemoveTask();
+            } catch (RemoteException | RuntimeException e) {
+                Slog.e(TAG, "Unable to stop illegal dream activity.");
+            }
+            return;
+        }
+
+        mCurrentDream.mAppTask = appTask;
+    }
+
+    /**
      * Stops dreaming.
      *
      * The current dream, if any, and any unstopped previous dreams are stopped. The device stops
@@ -303,8 +324,14 @@
                     mSentStartBroadcast = false;
                 }
 
-                mActivityTaskManager.removeRootTasksWithActivityTypes(
-                        new int[] {ACTIVITY_TYPE_DREAM});
+                if (mCurrentDream != null && mCurrentDream.mAppTask != null) {
+                    // Finish the dream task in case it hasn't finished by itself already.
+                    try {
+                        mCurrentDream.mAppTask.finishAndRemoveTask();
+                    } catch (RemoteException | RuntimeException e) {
+                        Slog.e(TAG, "Unable to stop dream activity.");
+                    }
+                }
 
                 mListener.onDreamStopped(dream.mToken);
             }
@@ -364,6 +391,7 @@
         public final boolean mIsPreviewMode;
         public final boolean mCanDoze;
         public final int mUserId;
+        public IAppTask mAppTask;
 
         public PowerManager.WakeLock mWakeLock;
         public boolean mBound;
diff --git a/services/core/java/com/android/server/dreams/DreamManagerService.java b/services/core/java/com/android/server/dreams/DreamManagerService.java
index 68cf59f..d88fe8a 100644
--- a/services/core/java/com/android/server/dreams/DreamManagerService.java
+++ b/services/core/java/com/android/server/dreams/DreamManagerService.java
@@ -27,6 +27,7 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.app.ActivityManager;
+import android.app.IAppTask;
 import android.app.TaskInfo;
 import android.content.BroadcastReceiver;
 import android.content.ComponentName;
@@ -37,6 +38,7 @@
 import android.content.pm.ActivityInfo;
 import android.content.pm.PackageManager;
 import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.pm.PackageManagerInternal;
 import android.content.pm.ServiceInfo;
 import android.database.ContentObserver;
 import android.hardware.display.AmbientDisplayConfiguration;
@@ -116,6 +118,7 @@
     private final PowerManagerInternal mPowerManagerInternal;
     private final PowerManager.WakeLock mDozeWakeLock;
     private final ActivityTaskManagerInternal mAtmInternal;
+    private final PackageManagerInternal mPmInternal;
     private final UserManager mUserManager;
     private final UiEventLogger mUiEventLogger;
     private final DreamUiEventLogger mDreamUiEventLogger;
@@ -216,6 +219,7 @@
         mPowerManager = (PowerManager)context.getSystemService(Context.POWER_SERVICE);
         mPowerManagerInternal = getLocalService(PowerManagerInternal.class);
         mAtmInternal = getLocalService(ActivityTaskManagerInternal.class);
+        mPmInternal = getLocalService(PackageManagerInternal.class);
         mUserManager = context.getSystemService(UserManager.class);
         mDozeWakeLock = mPowerManager.newWakeLock(PowerManager.DOZE_WAKE_LOCK, DOZE_WAKE_LOCK_TAG);
         mDozeConfig = new AmbientDisplayConfiguration(mContext);
@@ -1064,6 +1068,64 @@
                 Binder.restoreCallingIdentity(ident);
             }
         }
+
+        @Override // Binder call
+        public void startDreamActivity(@NonNull Intent intent) {
+            final int callingUid = Binder.getCallingUid();
+            final int callingPid = Binder.getCallingPid();
+            // We post here, because startDreamActivity and setDreamAppTask have to run
+            // synchronously and DreamController#setDreamAppTask has to run on mHandler.
+            mHandler.post(() -> {
+                final Binder dreamToken;
+                final String dreamPackageName;
+                synchronized (mLock) {
+                    if (mCurrentDream == null) {
+                        Slog.e(TAG, "Attempt to start DreamActivity, but the device is not "
+                                + "dreaming. Aborting without starting the DreamActivity.");
+                        return;
+                    }
+                    dreamToken = mCurrentDream.token;
+                    dreamPackageName = mCurrentDream.name.getPackageName();
+                }
+
+                if (!canLaunchDreamActivity(dreamPackageName, intent.getPackage(),
+                            callingUid)) {
+                    Slog.e(TAG, "The dream activity can be started only when the device is dreaming"
+                            + " and only by the active dream package.");
+                    return;
+                }
+
+                final IAppTask appTask = mAtmInternal.startDreamActivity(intent, callingUid,
+                        callingPid);
+                if (appTask == null) {
+                    Slog.e(TAG, "Could not start dream activity.");
+                    stopDreamInternal(true, "DreamActivity not started");
+                    return;
+                }
+                mController.setDreamAppTask(dreamToken, appTask);
+            });
+        }
+
+        boolean canLaunchDreamActivity(String dreamPackageName, String packageName,
+                int callingUid) {
+            if (dreamPackageName == null || packageName == null) {
+                Slog.e(TAG, "Cannot launch dream activity due to invalid state. dream component= "
+                        + dreamPackageName + ", packageName=" + packageName);
+                return false;
+            }
+            if (!mPmInternal.isSameApp(packageName, callingUid, UserHandle.getUserId(callingUid))) {
+                Slog.e(TAG, "Cannot launch dream activity because package="
+                        + packageName + " does not match callingUid=" + callingUid);
+                return false;
+            }
+            if (packageName.equals(dreamPackageName)) {
+                return true;
+            }
+            Slog.e(TAG, "Dream packageName does not match active dream. Package " + packageName
+                    + " does not match " + dreamPackageName);
+            return false;
+        }
+
     }
 
     private final class LocalService extends DreamManagerInternal {
diff --git a/services/core/java/com/android/server/input/InputManagerService.java b/services/core/java/com/android/server/input/InputManagerService.java
index 6a177e0..3a8f9d5 100644
--- a/services/core/java/com/android/server/input/InputManagerService.java
+++ b/services/core/java/com/android/server/input/InputManagerService.java
@@ -2800,6 +2800,11 @@
         void notifyConfigurationChanged();
 
         /**
+         * This callback is invoked when the pointer location changes.
+         */
+        void notifyPointerLocationChanged(boolean pointerLocationEnabled);
+
+        /**
          * This callback is invoked when the camera lens cover switch changes state.
          * @param whenNanos the time when the change occurred
          * @param lensCovered true is the lens is covered
@@ -3381,6 +3386,10 @@
         }
     }
 
+    void updatePointerLocationEnabled(boolean enabled) {
+        mWindowManagerCallbacks.notifyPointerLocationChanged(enabled);
+    }
+
     void updateFocusEventDebugViewEnabled(boolean enabled) {
         FocusEventDebugView view;
         synchronized (mFocusEventDebugViewLock) {
diff --git a/services/core/java/com/android/server/input/InputSettingsObserver.java b/services/core/java/com/android/server/input/InputSettingsObserver.java
index 42591f4..a608b4f 100644
--- a/services/core/java/com/android/server/input/InputSettingsObserver.java
+++ b/services/core/java/com/android/server/input/InputSettingsObserver.java
@@ -67,6 +67,8 @@
                         (reason) -> updateTouchpadRightClickZoneEnabled()),
                 Map.entry(Settings.System.getUriFor(Settings.System.SHOW_TOUCHES),
                         (reason) -> updateShowTouches()),
+                Map.entry(Settings.System.getUriFor(Settings.System.POINTER_LOCATION),
+                        (reason) -> updatePointerLocation()),
                 Map.entry(
                         Settings.Secure.getUriFor(Settings.Secure.ACCESSIBILITY_LARGE_POINTER_ICON),
                         (reason) -> updateAccessibilityLargePointer()),
@@ -149,6 +151,11 @@
         mNative.setShowTouches(getBoolean(Settings.System.SHOW_TOUCHES, false));
     }
 
+    private void updatePointerLocation() {
+        mService.updatePointerLocationEnabled(
+                getBoolean(Settings.System.POINTER_LOCATION, false));
+    }
+
     private void updateShowKeyPresses() {
         mService.updateFocusEventDebugViewEnabled(
                 getBoolean(Settings.System.SHOW_KEY_PRESSES, false));
diff --git a/services/core/java/com/android/server/inputmethod/HandwritingModeController.java b/services/core/java/com/android/server/inputmethod/HandwritingModeController.java
index 6a0550b..aa99dab 100644
--- a/services/core/java/com/android/server/inputmethod/HandwritingModeController.java
+++ b/services/core/java/com/android/server/inputmethod/HandwritingModeController.java
@@ -291,6 +291,10 @@
         reset(false /* reinitializing */);
     }
 
+    void setInkWindowInitializer(Runnable inkWindowInitializer) {
+        mInkWindowInitRunnable = inkWindowInitializer;
+    }
+
     private void reset(boolean reinitializing) {
         if (mHandwritingEventReceiver != null) {
             mHandwritingEventReceiver.dispose();
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index 716ecba..02ee96a 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;
 
@@ -2367,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() {
@@ -2449,6 +2472,9 @@
                 curInputMethodInfo != null && curInputMethodInfo.suppressesSpellChecker();
         final SparseArray<IAccessibilityInputMethodSession> accessibilityInputMethodSessions =
                 createAccessibilityInputMethodSessions(mCurClient.mAccessibilitySessions);
+        if (mBindingController.supportsStylusHandwriting() && hasSupportedStylusLocked()) {
+            mHwController.setInkWindowInitializer(new InkWindowInitializer());
+        }
         return new InputBindResult(InputBindResult.ResultCode.SUCCESS_WITH_IME_SESSION,
                 session.mSession, accessibilityInputMethodSessions,
                 (session.mChannel != null ? session.mChannel.dup() : null),
@@ -2838,6 +2864,8 @@
     @GuardedBy("ImfLock.class")
     void resetCurrentMethodAndClientLocked(@UnbindReason int unbindClientReason) {
         setSelectedMethodIdLocked(null);
+        // Callback before clean-up binding states.
+        onUnbindCurrentMethodByReset();
         mBindingController.unbindCurrentMethod();
         unbindCurrentClientLocked(unbindClientReason);
     }
diff --git a/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java b/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java
index 65e34e6..398e470 100644
--- a/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java
+++ b/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java
@@ -147,7 +147,7 @@
         mInjector = injector;
         mClock = injector.createClock();
         mDeathEaters = new ArrayMap<IBinder, IBinder.DeathRecipient>();
-        mCallbackDelegate = new CallbackDelegate();
+        mCallbackDelegate = new CallbackDelegate(injector.createCallbackLooper());
         mAppOps = (AppOpsManager) mContext.getSystemService(Context.APP_OPS_SERVICE);
         mActivityManagerInternal = LocalServices.getService(ActivityManagerInternal.class);
         mPackageManager = mContext.getPackageManager();
@@ -182,6 +182,11 @@
         Clock createClock() {
             return SystemClock::uptimeMillis;
         }
+
+        /** Creates the {@link Looper} to be used when notifying callbacks. */
+        Looper createCallbackLooper() {
+            return Looper.getMainLooper();
+        }
     }
 
     @Override
@@ -268,7 +273,8 @@
         dispatchStop(projection);
     }
 
-    private void addCallback(final IMediaProjectionWatcherCallback callback) {
+    @VisibleForTesting
+    void addCallback(final IMediaProjectionWatcherCallback callback) {
         IBinder.DeathRecipient deathRecipient = new IBinder.DeathRecipient() {
             @Override
             public void binderDied() {
@@ -315,6 +321,12 @@
         mCallbackDelegate.dispatchStop(projection);
     }
 
+    private void dispatchSessionSet(
+            @NonNull MediaProjectionInfo projectionInfo,
+            @Nullable ContentRecordingSession session) {
+        mCallbackDelegate.dispatchSession(projectionInfo, session);
+    }
+
     /**
      * Returns {@code true} when updating the current mirroring session on WM succeeded, and
      * {@code false} otherwise.
@@ -335,6 +347,7 @@
             if (mProjectionGrant != null) {
                 // Cache the session details.
                 mProjectionGrant.mSession = incomingSession;
+                dispatchSessionSet(mProjectionGrant.getProjectionInfo(), incomingSession);
             }
             return true;
         }
@@ -1155,8 +1168,8 @@
         private Handler mHandler;
         private final Object mLock = new Object();
 
-        public CallbackDelegate() {
-            mHandler = new Handler(Looper.getMainLooper(), null, true /*async*/);
+        CallbackDelegate(Looper callbackLooper) {
+            mHandler = new Handler(callbackLooper, null, true /*async*/);
             mClientCallbacks = new ArrayMap<IBinder, IMediaProjectionCallback>();
             mWatcherCallbacks = new ArrayMap<IBinder, IMediaProjectionWatcherCallback>();
         }
@@ -1219,6 +1232,16 @@
             }
         }
 
+        public void dispatchSession(
+                @NonNull MediaProjectionInfo projectionInfo,
+                @Nullable ContentRecordingSession session) {
+            synchronized (mLock) {
+                for (IMediaProjectionWatcherCallback callback : mWatcherCallbacks.values()) {
+                    mHandler.post(new WatcherSessionCallback(callback, projectionInfo, session));
+                }
+            }
+        }
+
         public void dispatchResize(MediaProjection projection, int width, int height) {
             if (projection == null) {
                 Slog.e(TAG,
@@ -1335,6 +1358,29 @@
         }
     }
 
+    private static final class WatcherSessionCallback implements Runnable {
+        private final IMediaProjectionWatcherCallback mCallback;
+        private final MediaProjectionInfo mProjectionInfo;
+        private final ContentRecordingSession mSession;
+
+        WatcherSessionCallback(
+                @NonNull IMediaProjectionWatcherCallback callback,
+                @NonNull MediaProjectionInfo projectionInfo,
+                @Nullable ContentRecordingSession session) {
+            mCallback = callback;
+            mProjectionInfo = projectionInfo;
+            mSession = session;
+        }
+
+        @Override
+        public void run() {
+            try {
+                mCallback.onRecordingSessionSet(mProjectionInfo, mSession);
+            } catch (RemoteException e) {
+                Slog.w(TAG, "Failed to notify content recording session changed", e);
+            }
+        }
+    }
 
     private static String typeToString(int type) {
         switch (type) {
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 5b32273..e0e6410 100644
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -5595,6 +5595,11 @@
                 boolean granted, boolean userSet) {
             Objects.requireNonNull(listener);
             checkNotificationListenerAccess();
+            if (granted && listener.flattenToString().length()
+                    > NotificationManager.MAX_SERVICE_COMPONENT_NAME_LENGTH) {
+                throw new IllegalArgumentException(
+                        "Component name too long: " + listener.flattenToString());
+            }
             if (!userSet && isNotificationListenerAccessUserSet(listener)) {
                 // Don't override user's choice
                 return;
@@ -9503,14 +9508,19 @@
      * Determine whether the userId applies to the notification in question, either because
      * they match exactly, or one of them is USER_ALL (which is treated as a wildcard).
      */
-    private static boolean notificationMatchesUserId(NotificationRecord r, int userId) {
-        return
+    private static boolean notificationMatchesUserId(NotificationRecord r, int userId,
+            boolean isAutogroupSummary) {
+        if (isAutogroupSummary) {
+            return r.getUserId() == userId;
+        } else {
+            return
                 // looking for USER_ALL notifications? match everything
-                   userId == UserHandle.USER_ALL
-                // a notification sent to USER_ALL matches any query
-                || r.getUserId() == UserHandle.USER_ALL
-                // an exact user match
-                || r.getUserId() == userId;
+                userId == UserHandle.USER_ALL
+                        // a notification sent to USER_ALL matches any query
+                        || r.getUserId() == UserHandle.USER_ALL
+                        // an exact user match
+                        || r.getUserId() == userId;
+        }
     }
 
     /**
@@ -9519,7 +9529,7 @@
      * because it matches one of the users profiles.
      */
     private boolean notificationMatchesCurrentProfiles(NotificationRecord r, int userId) {
-        return notificationMatchesUserId(r, userId)
+        return notificationMatchesUserId(r, userId, false)
                 || mUserProfiles.isCurrentProfile(r.getUserId());
     }
 
@@ -9588,7 +9598,7 @@
                 if (!notificationMatchesCurrentProfiles(r, userId)) {
                     continue;
                 }
-            } else if (!notificationMatchesUserId(r, userId)) {
+            } else if (!notificationMatchesUserId(r, userId, false)) {
                 continue;
             }
             // Don't remove notifications to all, if there's no package name specified
@@ -9826,7 +9836,7 @@
         final int len = list.size();
         for (int i = 0; i < len; i++) {
             NotificationRecord r = list.get(i);
-            if (notificationMatchesUserId(r, userId) && r.getGroupKey().equals(groupKey)
+            if (notificationMatchesUserId(r, userId, false) && r.getGroupKey().equals(groupKey)
                     && r.getSbn().getPackageName().equals(pkg)) {
                 records.add(r);
             }
@@ -9868,8 +9878,8 @@
         final int len = list.size();
         for (int i = 0; i < len; i++) {
             NotificationRecord r = list.get(i);
-            if (notificationMatchesUserId(r, userId) && r.getSbn().getId() == id &&
-                    TextUtils.equals(r.getSbn().getTag(), tag)
+            if (notificationMatchesUserId(r, userId, (r.getFlags() & GroupHelper.BASE_FLAGS) != 0)
+                    && r.getSbn().getId() == id && TextUtils.equals(r.getSbn().getTag(), tag)
                     && r.getSbn().getPackageName().equals(pkg)) {
                 return r;
             }
@@ -9883,8 +9893,8 @@
         final int len = list.size();
         for (int i = 0; i < len; i++) {
             NotificationRecord r = list.get(i);
-            if (notificationMatchesUserId(r, userId) && r.getSbn().getId() == id &&
-                    TextUtils.equals(r.getSbn().getTag(), tag)
+            if (notificationMatchesUserId(r, userId, false) && r.getSbn().getId() == id
+                    && TextUtils.equals(r.getSbn().getTag(), tag)
                     && r.getSbn().getPackageName().equals(pkg)) {
                 matching.add(r);
             }
diff --git a/services/core/java/com/android/server/pm/DeletePackageHelper.java b/services/core/java/com/android/server/pm/DeletePackageHelper.java
index 5f52c16..c1d5af5 100644
--- a/services/core/java/com/android/server/pm/DeletePackageHelper.java
+++ b/services/core/java/com/android/server/pm/DeletePackageHelper.java
@@ -610,7 +610,8 @@
                     PackageManager.UNINSTALL_REASON_UNKNOWN,
                     null /*harmfulAppWarning*/,
                     null /*splashScreenTheme*/,
-                    0 /*firstInstallTime*/);
+                    0 /*firstInstallTime*/,
+                    PackageManager.USER_MIN_ASPECT_RATIO_UNSET);
         }
         mPm.mSettings.writeKernelMappingLPr(ps);
     }
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index db47306..2fc22bf 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -30,6 +30,7 @@
 import static android.content.pm.PackageManager.MATCH_FACTORY_ONLY;
 import static android.content.pm.PackageManager.MATCH_SYSTEM_ONLY;
 import static android.content.pm.PackageManager.PERMISSION_GRANTED;
+import static android.content.pm.PackageManager.USER_MIN_ASPECT_RATIO_UNSET;
 import static android.os.Process.INVALID_UID;
 import static android.os.Trace.TRACE_TAG_PACKAGE_MANAGER;
 import static android.os.storage.StorageManager.FLAG_STORAGE_CE;
@@ -5239,6 +5240,20 @@
         }
 
         @Override
+        @PackageManager.UserMinAspectRatio
+        public int getUserMinAspectRatio(@NonNull String packageName, int userId) {
+            final Computer snapshot = snapshotComputer();
+            final int callingUid = Binder.getCallingUid();
+            snapshot.enforceCrossUserPermission(
+                    callingUid, userId, false /* requireFullPermission */,
+                    false /* checkShell */, "getUserMinAspectRatio");
+            final PackageStateInternal packageState = snapshot
+                    .getPackageStateForInstalledAndFiltered(packageName, callingUid, userId);
+            return packageState == null ? USER_MIN_ASPECT_RATIO_UNSET
+                    : packageState.getUserStateOrDefault(userId).getMinAspectRatio();
+        }
+
+        @Override
         public Bundle getSuspendedPackageAppExtras(String packageName, int userId) {
             final int callingUid = Binder.getCallingUid();
             final Computer snapshot = snapshot();
@@ -6201,6 +6216,32 @@
             return true;
         }
 
+        @android.annotation.EnforcePermission(android.Manifest.permission.INSTALL_PACKAGES)
+        @Override
+        public void setUserMinAspectRatio(@NonNull String packageName, int userId,
+                @PackageManager.UserMinAspectRatio int aspectRatio) {
+            setUserMinAspectRatio_enforcePermission();
+            final int callingUid = Binder.getCallingUid();
+            final Computer snapshot = snapshotComputer();
+            snapshot.enforceCrossUserPermission(callingUid, userId,
+                    false /* requireFullPermission */, false /* checkShell */,
+                    "setUserMinAspectRatio");
+            enforceOwnerRights(snapshot, packageName, callingUid);
+
+            final PackageStateInternal packageState = snapshot
+                    .getPackageStateForInstalledAndFiltered(packageName, callingUid, userId);
+            if (packageState == null) {
+                return;
+            }
+
+            if (packageState.getUserStateOrDefault(userId).getMinAspectRatio() == aspectRatio) {
+                return;
+            }
+
+            commitPackageStateMutation(null, packageName, state ->
+                    state.userState(userId).setMinAspectRatio(aspectRatio));
+        }
+
         @Override
         @SuppressWarnings("GuardedBy")
         public void setRuntimePermissionsVersion(int version, @UserIdInt int userId) {
diff --git a/services/core/java/com/android/server/pm/PackageSetting.java b/services/core/java/com/android/server/pm/PackageSetting.java
index 2411820..3e9ccac 100644
--- a/services/core/java/com/android/server/pm/PackageSetting.java
+++ b/services/core/java/com/android/server/pm/PackageSetting.java
@@ -875,7 +875,7 @@
             ArraySet<String> enabledComponents, ArraySet<String> disabledComponents,
             int installReason, int uninstallReason,
             String harmfulAppWarning, String splashScreenTheme,
-            long firstInstallTime) {
+            long firstInstallTime, int aspectRatio) {
         modifyUserState(userId)
                 .setSuspendParams(suspendParams)
                 .setCeDataInode(ceDataInode)
@@ -894,7 +894,8 @@
                 .setVirtualPreload(virtualPreload)
                 .setHarmfulAppWarning(harmfulAppWarning)
                 .setSplashScreenTheme(splashScreenTheme)
-                .setFirstInstallTimeMillis(firstInstallTime);
+                .setFirstInstallTimeMillis(firstInstallTime)
+                .setMinAspectRatio(aspectRatio);
         onChanged();
     }
 
@@ -912,7 +913,7 @@
                         ? null : otherState.getDisabledComponentsNoCopy().untrackedStorage(),
                 otherState.getInstallReason(), otherState.getUninstallReason(),
                 otherState.getHarmfulAppWarning(), otherState.getSplashScreenTheme(),
-                otherState.getFirstInstallTimeMillis());
+                otherState.getFirstInstallTimeMillis(), otherState.getMinAspectRatio());
     }
 
     WatchedArraySet<String> getEnabledComponents(int userId) {
diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java
index aaf13eb..532ae71 100644
--- a/services/core/java/com/android/server/pm/Settings.java
+++ b/services/core/java/com/android/server/pm/Settings.java
@@ -351,6 +351,7 @@
     private static final String ATTR_VIRTUAL_PRELOAD = "virtual-preload";
     private static final String ATTR_HARMFUL_APP_WARNING = "harmful-app-warning";
     private static final String ATTR_SPLASH_SCREEN_THEME = "splash-screen-theme";
+    private static final String ATTR_MIN_ASPECT_RATIO = "min-aspect-ratio";
 
     private static final String ATTR_PACKAGE_NAME = "packageName";
     private static final String ATTR_BUILD_FINGERPRINT = "buildFingerprint";
@@ -1122,7 +1123,8 @@
                                 PackageManager.UNINSTALL_REASON_UNKNOWN,
                                 null /*harmfulAppWarning*/,
                                 null /*splashscreenTheme*/,
-                                0 /*firstInstallTime*/
+                                0 /*firstInstallTime*/,
+                                PackageManager.USER_MIN_ASPECT_RATIO_UNSET
                         );
                     }
                 }
@@ -1776,7 +1778,8 @@
                                     PackageManager.UNINSTALL_REASON_UNKNOWN,
                                     null /*harmfulAppWarning*/,
                                     null /* splashScreenTheme*/,
-                                    0 /*firstInstallTime*/
+                                    0 /*firstInstallTime*/,
+                                    PackageManager.USER_MIN_ASPECT_RATIO_UNSET
                             );
                         }
                         return;
@@ -1871,6 +1874,9 @@
                                 ATTR_SPLASH_SCREEN_THEME);
                         final long firstInstallTime = parser.getAttributeLongHex(null,
                                 ATTR_FIRST_INSTALL_TIME, 0);
+                        final int minAspectRatio = parser.getAttributeInt(null,
+                                ATTR_MIN_ASPECT_RATIO,
+                                PackageManager.USER_MIN_ASPECT_RATIO_UNSET);
 
                         ArraySet<String> enabledComponents = null;
                         ArraySet<String> disabledComponents = null;
@@ -1947,7 +1953,8 @@
                                 enabledCaller, enabledComponents, disabledComponents, installReason,
                                 uninstallReason, harmfulAppWarning, splashScreenTheme,
                                 firstInstallTime != 0 ? firstInstallTime :
-                                        origFirstInstallTimes.getOrDefault(name, 0L));
+                                        origFirstInstallTimes.getOrDefault(name, 0L),
+                                minAspectRatio);
 
                         mDomainVerificationManager.setLegacyUserState(name, userId, verifState);
                     } else if (tagName.equals("preferred-activities")) {
@@ -2242,6 +2249,11 @@
                             serializer.attribute(null, ATTR_SPLASH_SCREEN_THEME,
                                     ustate.getSplashScreenTheme());
                         }
+                        if (ustate.getMinAspectRatio()
+                                != PackageManager.USER_MIN_ASPECT_RATIO_UNSET) {
+                            serializer.attributeInt(null, ATTR_MIN_ASPECT_RATIO,
+                                    ustate.getMinAspectRatio());
+                        }
                         if (ustate.isSuspended()) {
                             for (int i = 0; i < ustate.getSuspendParams().size(); i++) {
                                 final String suspendingPackage = ustate.getSuspendParams().keyAt(i);
diff --git a/services/core/java/com/android/server/pm/ShortcutService.java b/services/core/java/com/android/server/pm/ShortcutService.java
index 5b3514c..710e0b7 100644
--- a/services/core/java/com/android/server/pm/ShortcutService.java
+++ b/services/core/java/com/android/server/pm/ShortcutService.java
@@ -15,6 +15,7 @@
  */
 package com.android.server.pm;
 
+import static android.app.ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_DENIED;
 import static android.provider.DeviceConfig.NAMESPACE_SYSTEMUI;
 
 import android.Manifest.permission;
@@ -24,6 +25,7 @@
 import android.annotation.UserIdInt;
 import android.app.ActivityManager;
 import android.app.ActivityManagerInternal;
+import android.app.ActivityOptions;
 import android.app.AppGlobals;
 import android.app.IUidObserver;
 import android.app.IUriGrantsManager;
@@ -4407,8 +4409,11 @@
             return;
         }
         try {
+            ActivityOptions options = ActivityOptions.makeBasic()
+                    .setPendingIntentBackgroundActivityStartMode(
+                            MODE_BACKGROUND_ACTIVITY_START_DENIED);
             intentSender.sendIntent(mContext, /* code= */ 0, extras,
-                    /* onFinished=*/ null, /* handler= */ null);
+                    /* onFinished=*/ null, /* handler= */ null, null, options.toBundle());
         } catch (SendIntentException e) {
             Slog.w(TAG, "sendIntent failed().", e);
         }
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index 7e88e13..f8bd328 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -1037,7 +1037,7 @@
                 final UserData userData = mUsers.valueAt(i);
                 final int userId = userData.info.id;
                 if (userId != currentUser && userData.info.isFull() && !userData.info.partial
-                        && !mRemovingUserIds.get(userId)) {
+                        && userData.info.isEnabled() && !mRemovingUserIds.get(userId)) {
                     final long userEnteredTime = userData.mLastEnteredForegroundTimeMillis;
                     if (userEnteredTime > latestEnteredTime) {
                         latestEnteredTime = userEnteredTime;
@@ -2927,14 +2927,14 @@
             Preconditions.checkState(mCachedEffectiveUserRestrictions.getRestrictions(userId)
                     != newBaseRestrictions);
 
-            if (mBaseUserRestrictions.updateRestrictions(userId, newBaseRestrictions)) {
+            if (mBaseUserRestrictions.updateRestrictions(userId, new Bundle(newBaseRestrictions))) {
                 scheduleWriteUser(userId);
             }
         }
 
         final Bundle effective = computeEffectiveUserRestrictionsLR(userId);
 
-        mCachedEffectiveUserRestrictions.updateRestrictions(userId, effective);
+        mCachedEffectiveUserRestrictions.updateRestrictions(userId, new Bundle(effective));
 
         // Apply the new restrictions.
         if (DBG) {
@@ -5589,8 +5589,14 @@
         }
     }
 
-    @GuardedBy("mUsersLock")
     @VisibleForTesting
+    void addRemovingUserId(@UserIdInt int userId) {
+        synchronized (mUsersLock) {
+            addRemovingUserIdLocked(userId);
+        }
+    }
+
+    @GuardedBy("mUsersLock")
     void addRemovingUserIdLocked(@UserIdInt int userId) {
         // We remember deleted user IDs to prevent them from being
         // reused during the current boot; they can still be reused
diff --git a/services/core/java/com/android/server/pm/pkg/PackageStateImpl.java b/services/core/java/com/android/server/pm/pkg/PackageStateImpl.java
index 91a25db3..3d056e8 100644
--- a/services/core/java/com/android/server/pm/pkg/PackageStateImpl.java
+++ b/services/core/java/com/android/server/pm/pkg/PackageStateImpl.java
@@ -377,6 +377,8 @@
         private final int mUninstallReason;
         @Nullable
         private final String mSplashScreenTheme;
+        @PackageManager.UserMinAspectRatio
+        private final int mMinAspectRatio;
         private final long mFirstInstallTimeMillis;
 
         private UserStateImpl(@NonNull PackageUserState userState) {
@@ -392,6 +394,7 @@
             mSharedLibraryOverlayPaths = userState.getSharedLibraryOverlayPaths();
             mUninstallReason = userState.getUninstallReason();
             mSplashScreenTheme = userState.getSplashScreenTheme();
+            mMinAspectRatio = userState.getMinAspectRatio();
             setBoolean(Booleans.HIDDEN, userState.isHidden());
             setBoolean(Booleans.INSTALLED, userState.isInstalled());
             setBoolean(Booleans.INSTANT_APP, userState.isInstantApp());
@@ -543,6 +546,11 @@
         }
 
         @DataClass.Generated.Member
+        public @PackageManager.UserMinAspectRatio int getMinAspectRatio() {
+            return mMinAspectRatio;
+        }
+
+        @DataClass.Generated.Member
         public long getFirstInstallTimeMillis() {
             return mFirstInstallTimeMillis;
         }
@@ -554,10 +562,10 @@
         }
 
         @DataClass.Generated(
-                time = 1671671043891L,
+                time = 1687938966108L,
                 codegenVersion = "1.0.23",
                 sourceFile = "frameworks/base/services/core/java/com/android/server/pm/pkg/PackageStateImpl.java",
-                inputSignatures = "private  int mBooleans\nprivate final  long mCeDataInode\nprivate final @android.annotation.NonNull android.util.ArraySet<java.lang.String> mDisabledComponents\nprivate final @android.content.pm.PackageManager.DistractionRestriction int mDistractionFlags\nprivate final @android.annotation.NonNull android.util.ArraySet<java.lang.String> mEnabledComponents\nprivate final  int mEnabledState\nprivate final @android.annotation.Nullable java.lang.String mHarmfulAppWarning\nprivate final @android.content.pm.PackageManager.InstallReason int mInstallReason\nprivate final @android.annotation.Nullable java.lang.String mLastDisableAppCaller\nprivate final @android.annotation.NonNull android.content.pm.overlay.OverlayPaths mOverlayPaths\nprivate final @android.annotation.NonNull java.util.Map<java.lang.String,android.content.pm.overlay.OverlayPaths> mSharedLibraryOverlayPaths\nprivate final @android.content.pm.PackageManager.UninstallReason int mUninstallReason\nprivate final @android.annotation.Nullable java.lang.String mSplashScreenTheme\nprivate final  long mFirstInstallTimeMillis\npublic static  com.android.server.pm.pkg.PackageUserState copy(com.android.server.pm.pkg.PackageUserState)\nprivate  void setBoolean(int,boolean)\nprivate  boolean getBoolean(int)\npublic @java.lang.Override boolean isHidden()\npublic @java.lang.Override boolean isInstalled()\npublic @java.lang.Override boolean isInstantApp()\npublic @java.lang.Override boolean isNotLaunched()\npublic @java.lang.Override boolean isStopped()\npublic @java.lang.Override boolean isSuspended()\npublic @java.lang.Override boolean isVirtualPreload()\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()\nclass UserStateImpl extends java.lang.Object implements [com.android.server.pm.pkg.PackageUserState]\nprivate static final  int HIDDEN\nprivate static final  int INSTALLED\nprivate static final  int INSTANT_APP\nprivate static final  int NOT_LAUNCHED\nprivate static final  int STOPPED\nprivate static final  int SUSPENDED\nprivate static final  int VIRTUAL_PRELOAD\nclass Booleans extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genConstructor=false)")
+                inputSignatures = "private  int mBooleans\nprivate final  long mCeDataInode\nprivate final @android.annotation.NonNull android.util.ArraySet<java.lang.String> mDisabledComponents\nprivate final @android.content.pm.PackageManager.DistractionRestriction int mDistractionFlags\nprivate final @android.annotation.NonNull android.util.ArraySet<java.lang.String> mEnabledComponents\nprivate final  int mEnabledState\nprivate final @android.annotation.Nullable java.lang.String mHarmfulAppWarning\nprivate final @android.content.pm.PackageManager.InstallReason int mInstallReason\nprivate final @android.annotation.Nullable java.lang.String mLastDisableAppCaller\nprivate final @android.annotation.NonNull android.content.pm.overlay.OverlayPaths mOverlayPaths\nprivate final @android.annotation.NonNull java.util.Map<java.lang.String,android.content.pm.overlay.OverlayPaths> mSharedLibraryOverlayPaths\nprivate final @android.content.pm.PackageManager.UninstallReason int mUninstallReason\nprivate final @android.annotation.Nullable java.lang.String mSplashScreenTheme\nprivate final @android.content.pm.PackageManager.UserMinAspectRatio int mMinAspectRatio\nprivate final  long mFirstInstallTimeMillis\npublic static  com.android.server.pm.pkg.PackageUserState copy(com.android.server.pm.pkg.PackageUserState)\nprivate  void setBoolean(int,boolean)\nprivate  boolean getBoolean(int)\npublic @java.lang.Override boolean isHidden()\npublic @java.lang.Override boolean isInstalled()\npublic @java.lang.Override boolean isInstantApp()\npublic @java.lang.Override boolean isNotLaunched()\npublic @java.lang.Override boolean isStopped()\npublic @java.lang.Override boolean isSuspended()\npublic @java.lang.Override boolean isVirtualPreload()\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()\nclass UserStateImpl extends java.lang.Object implements [com.android.server.pm.pkg.PackageUserState]\nprivate static final  int HIDDEN\nprivate static final  int INSTALLED\nprivate static final  int INSTANT_APP\nprivate static final  int NOT_LAUNCHED\nprivate static final  int STOPPED\nprivate static final  int SUSPENDED\nprivate static final  int VIRTUAL_PRELOAD\nclass Booleans extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genConstructor=false)")
         @Deprecated
         private void __metadata() {}
 
diff --git a/services/core/java/com/android/server/pm/pkg/PackageUserState.java b/services/core/java/com/android/server/pm/pkg/PackageUserState.java
index 2048d65..f75d214 100644
--- a/services/core/java/com/android/server/pm/pkg/PackageUserState.java
+++ b/services/core/java/com/android/server/pm/pkg/PackageUserState.java
@@ -217,4 +217,12 @@
      */
     @Nullable
     String getSplashScreenTheme();
+
+    /**
+     * @return the min aspect ratio setting of the package which by default is unset
+     * unless it has been set by the user
+     * @hide
+     */
+    @PackageManager.UserMinAspectRatio
+    int getMinAspectRatio();
 }
diff --git a/services/core/java/com/android/server/pm/pkg/PackageUserStateDefault.java b/services/core/java/com/android/server/pm/pkg/PackageUserStateDefault.java
index 73fb672..1fb12a8 100644
--- a/services/core/java/com/android/server/pm/pkg/PackageUserStateDefault.java
+++ b/services/core/java/com/android/server/pm/pkg/PackageUserStateDefault.java
@@ -136,6 +136,11 @@
     }
 
     @Override
+    public @PackageManager.UserMinAspectRatio int getMinAspectRatio() {
+        return PackageManager.USER_MIN_ASPECT_RATIO_UNSET;
+    }
+
+    @Override
     public long getFirstInstallTimeMillis() {
         return 0;
     }
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 e8e2d41..d911ac1 100644
--- a/services/core/java/com/android/server/pm/pkg/PackageUserStateImpl.java
+++ b/services/core/java/com/android/server/pm/pkg/PackageUserStateImpl.java
@@ -83,6 +83,9 @@
     @Nullable
     private String mSplashScreenTheme;
 
+    @PackageManager.UserMinAspectRatio
+    private int mMinAspectRatio = PackageManager.USER_MIN_ASPECT_RATIO_UNSET;
+
     /**
      * Suspending package to suspend params
      */
@@ -146,6 +149,7 @@
         mHarmfulAppWarning = other.mHarmfulAppWarning;
         mLastDisableAppCaller = other.mLastDisableAppCaller;
         mSplashScreenTheme = other.mSplashScreenTheme;
+        mMinAspectRatio = other.mMinAspectRatio;
         mSuspendParams = other.mSuspendParams == null ? null : other.mSuspendParams.snapshot();
         mComponentLabelIconOverrideMap = other.mComponentLabelIconOverrideMap == null
                 ? null : other.mComponentLabelIconOverrideMap.snapshot();
@@ -508,6 +512,19 @@
     }
 
     /**
+     * Sets user min aspect ratio override value
+     * @see PackageManager.UserMinAspectRatio
+     */
+    public @NonNull PackageUserStateImpl setMinAspectRatio(
+            @PackageManager.UserMinAspectRatio int value) {
+        mMinAspectRatio = value;
+        com.android.internal.util.AnnotationValidations.validate(
+                PackageManager.UserMinAspectRatio.class, null, mMinAspectRatio);
+        onChanged();
+        return this;
+    }
+
+    /**
      * Suspending package to suspend params
      */
     public @NonNull PackageUserStateImpl setSuspendParams(
@@ -679,6 +696,11 @@
         return mSplashScreenTheme;
     }
 
+    @DataClass.Generated.Member
+    public @PackageManager.UserMinAspectRatio int getMinAspectRatio() {
+        return mMinAspectRatio;
+    }
+
     /**
      * Suspending package to suspend params
      */
@@ -766,6 +788,7 @@
                 && Objects.equals(mOverlayPaths, that.mOverlayPaths)
                 && Objects.equals(mSharedLibraryOverlayPaths, that.mSharedLibraryOverlayPaths)
                 && Objects.equals(mSplashScreenTheme, that.mSplashScreenTheme)
+                && mMinAspectRatio == that.mMinAspectRatio
                 && Objects.equals(mSuspendParams, that.mSuspendParams)
                 && Objects.equals(mComponentLabelIconOverrideMap, that.mComponentLabelIconOverrideMap)
                 && mFirstInstallTimeMillis == that.mFirstInstallTimeMillis
@@ -798,6 +821,7 @@
         _hash = 31 * _hash + Objects.hashCode(mOverlayPaths);
         _hash = 31 * _hash + Objects.hashCode(mSharedLibraryOverlayPaths);
         _hash = 31 * _hash + Objects.hashCode(mSplashScreenTheme);
+        _hash = 31 * _hash + mMinAspectRatio;
         _hash = 31 * _hash + Objects.hashCode(mSuspendParams);
         _hash = 31 * _hash + Objects.hashCode(mComponentLabelIconOverrideMap);
         _hash = 31 * _hash + Long.hashCode(mFirstInstallTimeMillis);
@@ -807,10 +831,10 @@
     }
 
     @DataClass.Generated(
-            time = 1686952839807L,
+            time = 1687938397579L,
             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 @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)")
+            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.content.pm.PackageManager.UserMinAspectRatio int mMinAspectRatio\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 setMinAspectRatio(int)\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/pm/pkg/mutate/PackageStateMutator.java b/services/core/java/com/android/server/pm/pkg/mutate/PackageStateMutator.java
index 8125b0f..8430cf7 100644
--- a/services/core/java/com/android/server/pm/pkg/mutate/PackageStateMutator.java
+++ b/services/core/java/com/android/server/pm/pkg/mutate/PackageStateMutator.java
@@ -439,6 +439,16 @@
                 }
                 return null;
             }
+
+            @NonNull
+            @Override
+            public PackageUserStateWrite setMinAspectRatio(
+                    @PackageManager.UserMinAspectRatio int aspectRatio) {
+                if (mUserState != null) {
+                    mUserState.setMinAspectRatio(aspectRatio);
+                }
+                return this;
+            }
         }
     }
 }
diff --git a/services/core/java/com/android/server/pm/pkg/mutate/PackageUserStateWrite.java b/services/core/java/com/android/server/pm/pkg/mutate/PackageUserStateWrite.java
index 11d6d97..0c6c672 100644
--- a/services/core/java/com/android/server/pm/pkg/mutate/PackageUserStateWrite.java
+++ b/services/core/java/com/android/server/pm/pkg/mutate/PackageUserStateWrite.java
@@ -22,6 +22,7 @@
 import android.content.pm.PackageManager;
 import android.content.pm.overlay.OverlayPaths;
 
+import com.android.server.pm.pkg.PackageUserStateImpl;
 import com.android.server.pm.pkg.SuspendParams;
 
 public interface PackageUserStateWrite {
@@ -68,4 +69,8 @@
     @NonNull
     PackageUserStateWrite setComponentLabelIcon(@NonNull ComponentName componentName,
             @Nullable String nonLocalizedLabel, @Nullable Integer icon);
+
+    /** @see PackageUserStateImpl#setMinAspectRatio(int) */
+    @NonNull
+    PackageUserStateWrite setMinAspectRatio(@PackageManager.UserMinAspectRatio int aspectRatio);
 }
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index a792b9c..279a480 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -91,6 +91,7 @@
 import android.accessibilityservice.AccessibilityServiceInfo;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.SuppressLint;
 import android.app.ActivityManager;
 import android.app.ActivityManager.RecentTaskInfo;
 import android.app.ActivityManagerInternal;
@@ -632,6 +633,8 @@
 
     SettingsObserver mSettingsObserver;
     ModifierShortcutManager mModifierShortcutManager;
+    /** Currently fully consumed key codes per device */
+    private final SparseArray<Set<Integer>> mConsumedKeysForDevice = new SparseArray<>();
     PowerManager.WakeLock mBroadcastWakeLock;
     PowerManager.WakeLock mPowerKeyWakeLock;
     boolean mHavePendingMediaKeyRepeatWithWakeLock;
@@ -708,8 +711,11 @@
                     finishKeyguardDrawn();
                     break;
                 case MSG_WINDOW_MANAGER_DRAWN_COMPLETE:
-                    if (DEBUG_WAKEUP) Slog.w(TAG, "Setting mWindowManagerDrawComplete");
-                    finishWindowsDrawn(msg.arg1);
+                    final int displayId = msg.arg1;
+                    if (DEBUG_WAKEUP) Slog.w(TAG, "All windows drawn on display " + displayId);
+                    Trace.asyncTraceEnd(Trace.TRACE_TAG_WINDOW_MANAGER,
+                            TRACE_WAIT_FOR_ALL_WINDOWS_DRAWN_METHOD, displayId /* cookie */);
+                    finishWindowsDrawn(displayId);
                     break;
                 case MSG_HIDE_BOOT_MESSAGE:
                     handleHideBootMessage();
@@ -1814,7 +1820,7 @@
             mDisplayId = displayId;
         }
 
-        int handleHomeButton(IBinder focusedToken, KeyEvent event) {
+        boolean handleHomeButton(IBinder focusedToken, KeyEvent event) {
             final boolean keyguardOn = keyguardOn();
             final int repeatCount = event.getRepeatCount();
             final boolean down = event.getAction() == KeyEvent.ACTION_DOWN;
@@ -1835,12 +1841,12 @@
                 mHomePressed = false;
                 if (mHomeConsumed) {
                     mHomeConsumed = false;
-                    return -1;
+                    return true;
                 }
 
                 if (canceled) {
                     Log.i(TAG, "Ignoring HOME; event canceled.");
-                    return -1;
+                    return true;
                 }
 
                 // Delay handling home if a double-tap is possible.
@@ -1852,13 +1858,13 @@
                         mHomeDoubleTapPending = true;
                         mHandler.postDelayed(mHomeDoubleTapTimeoutRunnable,
                                 ViewConfiguration.getDoubleTapTimeout());
-                        return -1;
+                        return true;
                     }
                 }
 
                 // Post to main thread to avoid blocking input pipeline.
                 mHandler.post(() -> handleShortPressOnHome(mDisplayId));
-                return -1;
+                return true;
             }
 
             final KeyInterceptionInfo info =
@@ -1870,12 +1876,12 @@
                         || (info.layoutParamsType == TYPE_NOTIFICATION_SHADE
                         && isKeyguardShowing())) {
                     // the "app" is keyguard, so give it the key
-                    return 0;
+                    return false;
                 }
                 for (int t : WINDOW_TYPES_WHERE_HOME_DOESNT_WORK) {
                     if (info.layoutParamsType == t) {
                         // don't do anything, but also don't pass it to the app
-                        return -1;
+                        return true;
                     }
                 }
             }
@@ -1900,7 +1906,7 @@
                             event.getEventTime()));
                 }
             }
-            return -1;
+            return true;
         }
 
         private void handleDoubleTapOnHome() {
@@ -2946,24 +2952,21 @@
     @Override
     public long interceptKeyBeforeDispatching(IBinder focusedToken, KeyEvent event,
             int policyFlags) {
-        final boolean keyguardOn = keyguardOn();
         final int keyCode = event.getKeyCode();
-        final int repeatCount = event.getRepeatCount();
-        final int metaState = event.getMetaState();
         final int flags = event.getFlags();
-        final boolean down = event.getAction() == KeyEvent.ACTION_DOWN;
-        final boolean canceled = event.isCanceled();
-        final int displayId = event.getDisplayId();
-        final long key_consumed = -1;
-        final long key_not_consumed = 0;
+        final long keyConsumed = -1;
+        final long keyNotConsumed = 0;
+        final int deviceId = event.getDeviceId();
 
         if (DEBUG_INPUT) {
-            Log.d(TAG, "interceptKeyTi keyCode=" + keyCode + " down=" + down + " repeatCount="
-                    + repeatCount + " keyguardOn=" + keyguardOn + " canceled=" + canceled);
+            Log.d(TAG,
+                    "interceptKeyTi keyCode=" + keyCode + " action=" + event.getAction()
+                            + " repeatCount=" + event.getRepeatCount() + " keyguardOn="
+                            + keyguardOn() + " canceled=" + event.isCanceled());
         }
 
         if (mKeyCombinationManager.isKeyConsumed(event)) {
-            return key_consumed;
+            return keyConsumed;
         }
 
         if ((flags & KeyEvent.FLAG_FALLBACK) == 0) {
@@ -2974,8 +2977,54 @@
             }
         }
 
-        // Cancel any pending meta actions if we see any other keys being pressed between the down
-        // of the meta key and its corresponding up.
+        Set<Integer> consumedKeys = mConsumedKeysForDevice.get(deviceId);
+        if (consumedKeys == null) {
+            consumedKeys = new HashSet<>();
+            mConsumedKeysForDevice.put(deviceId, consumedKeys);
+        }
+
+        if (interceptSystemKeysAndShortcuts(focusedToken, event)
+                && event.getAction() == KeyEvent.ACTION_DOWN && event.getRepeatCount() == 0) {
+            consumedKeys.add(keyCode);
+            return keyConsumed;
+        }
+
+        boolean needToConsumeKey = consumedKeys.contains(keyCode);
+        if (event.getAction() == KeyEvent.ACTION_UP || event.isCanceled()) {
+            consumedKeys.remove(keyCode);
+            if (consumedKeys.isEmpty()) {
+                mConsumedKeysForDevice.remove(deviceId);
+            }
+        }
+
+        return needToConsumeKey ? keyConsumed : keyNotConsumed;
+    }
+
+    // You can only start consuming the key gesture if ACTION_DOWN and repeat count
+    // is 0. If you start intercepting the key halfway, then key will not be consumed
+    // and will be sent to apps for processing too.
+    // e.g. If a certain combination is only handled on ACTION_UP (i.e.
+    // interceptShortcut() returns true only for ACTION_UP), then since we already
+    // sent the ACTION_DOWN events to the application, we MUST also send the
+    // ACTION_UP to the application.
+    // So, to ensure that your intercept logic works properly, and we don't send any
+    // conflicting events to application, make sure to consume the event on
+    // ACTION_DOWN even if you want to do something on ACTION_UP. This is essential
+    // to maintain event parity and to not have incomplete key gestures.
+    @SuppressLint("MissingPermission")
+    private boolean interceptSystemKeysAndShortcuts(IBinder focusedToken, KeyEvent event) {
+        final boolean keyguardOn = keyguardOn();
+        final int keyCode = event.getKeyCode();
+        final int repeatCount = event.getRepeatCount();
+        final int metaState = event.getMetaState();
+        final boolean down = event.getAction() == KeyEvent.ACTION_DOWN;
+        final boolean canceled = event.isCanceled();
+        final int displayId = event.getDisplayId();
+        final int deviceId = event.getDeviceId();
+        final boolean firstDown = down && repeatCount == 0;
+
+        // Cancel any pending meta actions if we see any other keys being pressed between the
+        // down of the meta key and its corresponding up.
         if (mPendingMetaAction && !KeyEvent.isMetaKey(keyCode)) {
             mPendingMetaAction = false;
         }
@@ -2989,50 +3038,49 @@
                 dismissKeyboardShortcutsMenu();
                 mPendingMetaAction = false;
                 mPendingCapsLockToggle = false;
-                return key_consumed;
+                return true;
             }
         }
 
-        switch(keyCode) {
+        switch (keyCode) {
             case KeyEvent.KEYCODE_HOME:
-                logKeyboardSystemsEvent(event, FrameworkStatsLog
-                        .KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__HOME);
+                logKeyboardSystemsEvent(event,
+                        FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__HOME);
                 return handleHomeShortcuts(displayId, focusedToken, event);
             case KeyEvent.KEYCODE_MENU:
                 // Hijack modified menu keys for debugging features
                 final int chordBug = KeyEvent.META_SHIFT_ON;
 
-                if (down && repeatCount == 0) {
-                    if (mEnableShiftMenuBugReports && (metaState & chordBug) == chordBug) {
-                        Intent intent = new Intent(Intent.ACTION_BUG_REPORT);
-                        mContext.sendOrderedBroadcastAsUser(intent, UserHandle.CURRENT,
-                                null, null, null, 0, null, null);
-                        return key_consumed;
-                    }
+                if (mEnableShiftMenuBugReports && firstDown
+                        && (metaState & chordBug) == chordBug) {
+                    Intent intent = new Intent(Intent.ACTION_BUG_REPORT);
+                    mContext.sendOrderedBroadcastAsUser(intent, UserHandle.CURRENT,
+                            null, null, null, 0, null, null);
+                    return true;
                 }
                 break;
             case KeyEvent.KEYCODE_RECENT_APPS:
-                if (down && repeatCount == 0) {
+                if (firstDown) {
                     showRecentApps(false /* triggeredFromAltTab */);
-                    logKeyboardSystemsEvent(event, FrameworkStatsLog
-                            .KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__RECENT_APPS);
+                    logKeyboardSystemsEvent(event,
+                            FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__RECENT_APPS);
                 }
-                return key_consumed;
+                return true;
             case KeyEvent.KEYCODE_APP_SWITCH:
                 if (!keyguardOn) {
-                    if (down && repeatCount == 0) {
+                    if (firstDown) {
                         preloadRecentApps();
                     } else if (!down) {
                         toggleRecentApps();
                     }
                 }
-                return key_consumed;
+                return true;
             case KeyEvent.KEYCODE_A:
-                if (down && event.isMetaPressed()) {
+                if (firstDown && event.isMetaPressed()) {
                     launchAssistAction(Intent.EXTRA_ASSIST_INPUT_HINT_KEYBOARD,
-                            event.getDeviceId(),
-                            event.getEventTime(), AssistUtils.INVOCATION_TYPE_UNKNOWN);
-                    return key_consumed;
+                            deviceId, event.getEventTime(),
+                            AssistUtils.INVOCATION_TYPE_UNKNOWN);
+                    return true;
                 }
                 break;
             case KeyEvent.KEYCODE_H:
@@ -3042,73 +3090,73 @@
                 }
                 break;
             case KeyEvent.KEYCODE_I:
-                if (down && event.isMetaPressed()) {
+                if (firstDown && event.isMetaPressed()) {
                     showSystemSettings();
-                    return key_consumed;
+                    return true;
                 }
                 break;
             case KeyEvent.KEYCODE_L:
-                if (down && event.isMetaPressed() && repeatCount == 0) {
+                if (firstDown && event.isMetaPressed()) {
                     lockNow(null /* options */);
-                    return key_consumed;
+                    return true;
                 }
                 break;
             case KeyEvent.KEYCODE_N:
-                if (down && event.isMetaPressed()) {
+                if (firstDown && event.isMetaPressed()) {
                     if (event.isCtrlPressed()) {
                         sendSystemKeyToStatusBarAsync(event);
                     } else {
                         toggleNotificationPanel();
                     }
-                    return key_consumed;
+                    return true;
                 }
                 break;
             case KeyEvent.KEYCODE_S:
-                if (down && event.isMetaPressed() && event.isCtrlPressed() && repeatCount == 0) {
+                if (firstDown && event.isMetaPressed() && event.isCtrlPressed()) {
                     interceptScreenshotChord(SCREENSHOT_KEY_OTHER, 0 /*pressDelay*/);
-                    return key_consumed;
+                    return true;
                 }
                 break;
             case KeyEvent.KEYCODE_T:
-                if (down && event.isMetaPressed()) {
+                if (firstDown && event.isMetaPressed()) {
                     toggleTaskbar();
-                    return key_consumed;
+                    return true;
                 }
                 break;
             case KeyEvent.KEYCODE_DPAD_UP:
-                if (down && event.isMetaPressed() && event.isCtrlPressed() && repeatCount == 0) {
+                if (firstDown && event.isMetaPressed() && event.isCtrlPressed()) {
                     StatusBarManagerInternal statusbar = getStatusBarManagerInternal();
                     if (statusbar != null) {
                         statusbar.goToFullscreenFromSplit();
+                        return true;
                     }
-                    return key_consumed;
                 }
                 break;
             case KeyEvent.KEYCODE_DPAD_LEFT:
-                if (down && event.isMetaPressed() && event.isCtrlPressed() && repeatCount == 0) {
+                if (firstDown && event.isMetaPressed() && event.isCtrlPressed()) {
                     enterStageSplitFromRunningApp(true /* leftOrTop */);
-                    return key_consumed;
+                    return true;
                 }
                 break;
             case KeyEvent.KEYCODE_DPAD_RIGHT:
-                if (down && event.isMetaPressed() && event.isCtrlPressed() && repeatCount == 0) {
+                if (firstDown && event.isMetaPressed() && event.isCtrlPressed()) {
                     enterStageSplitFromRunningApp(false /* leftOrTop */);
-                    return key_consumed;
+                    return true;
                 }
                 break;
             case KeyEvent.KEYCODE_SLASH:
-                if (down && repeatCount == 0 && event.isMetaPressed() && !keyguardOn) {
+                if (firstDown && event.isMetaPressed() && !keyguardOn) {
                     toggleKeyboardShortcutsMenu(event.getDeviceId());
-                    return key_consumed;
+                    return true;
                 }
                 break;
             case KeyEvent.KEYCODE_ASSIST:
                 Slog.wtf(TAG, "KEYCODE_ASSIST should be handled in interceptKeyBeforeQueueing");
-                return key_consumed;
+                return true;
             case KeyEvent.KEYCODE_VOICE_ASSIST:
                 Slog.wtf(TAG, "KEYCODE_VOICE_ASSIST should be handled in"
                         + " interceptKeyBeforeQueueing");
-                return key_consumed;
+                return true;
             case KeyEvent.KEYCODE_VIDEO_APP_1:
             case KeyEvent.KEYCODE_VIDEO_APP_2:
             case KeyEvent.KEYCODE_VIDEO_APP_3:
@@ -3126,7 +3174,7 @@
             case KeyEvent.KEYCODE_DEMO_APP_3:
             case KeyEvent.KEYCODE_DEMO_APP_4:
                 Slog.wtf(TAG, "KEYCODE_APP_X should be handled in interceptKeyBeforeQueueing");
-                return key_consumed;
+                return true;
             case KeyEvent.KEYCODE_BRIGHTNESS_UP:
             case KeyEvent.KEYCODE_BRIGHTNESS_DOWN:
                 if (down) {
@@ -3166,20 +3214,20 @@
                     startActivityAsUser(new Intent(Intent.ACTION_SHOW_BRIGHTNESS_DIALOG),
                             UserHandle.CURRENT_OR_SELF);
                 }
-                return key_consumed;
+                return true;
             case KeyEvent.KEYCODE_KEYBOARD_BACKLIGHT_DOWN:
                 if (down) {
                     mInputManagerInternal.decrementKeyboardBacklight(event.getDeviceId());
                 }
-                return key_consumed;
+                return true;
             case KeyEvent.KEYCODE_KEYBOARD_BACKLIGHT_UP:
                 if (down) {
                     mInputManagerInternal.incrementKeyboardBacklight(event.getDeviceId());
                 }
-                return key_consumed;
+                return true;
             case KeyEvent.KEYCODE_KEYBOARD_BACKLIGHT_TOGGLE:
                 // TODO: Add logic
-                return key_consumed;
+                return true;
             case KeyEvent.KEYCODE_VOLUME_UP:
             case KeyEvent.KEYCODE_VOLUME_DOWN:
             case KeyEvent.KEYCODE_VOLUME_MUTE:
@@ -3187,7 +3235,7 @@
                     // On TVs or when the configuration is enabled, volume keys never
                     // go to the foreground app.
                     dispatchDirectAudioEvent(event);
-                    return key_consumed;
+                    return true;
                 }
 
                 // If the device is in VR mode and keys are "internal" (e.g. on the side of the
@@ -3196,26 +3244,23 @@
                 if (mDefaultDisplayPolicy.isPersistentVrModeEnabled()) {
                     final InputDevice d = event.getDevice();
                     if (d != null && !d.isExternal()) {
-                        return key_consumed;
+                        return true;
                     }
                 }
                 break;
             case KeyEvent.KEYCODE_TAB:
-                if (down && event.isMetaPressed()) {
-                    if (!keyguardOn && isUserSetupComplete()) {
+                if (firstDown && !keyguardOn && isUserSetupComplete()) {
+                    if (event.isMetaPressed()) {
                         showRecentApps(false);
-                        return key_consumed;
-                    }
-                } else if (down && repeatCount == 0) {
-                    // Display task switcher for ALT-TAB.
-                    if (mRecentAppsHeldModifiers == 0 && !keyguardOn && isUserSetupComplete()) {
+                        return true;
+                    } else if (mRecentAppsHeldModifiers == 0) {
                         final int shiftlessModifiers =
                                 event.getModifiers() & ~KeyEvent.META_SHIFT_MASK;
                         if (KeyEvent.metaStateHasModifiers(
                                 shiftlessModifiers, KeyEvent.META_ALT_ON)) {
                             mRecentAppsHeldModifiers = shiftlessModifiers;
                             showRecentApps(true);
-                            return key_consumed;
+                            return true;
                         }
                     }
                 }
@@ -3227,18 +3272,18 @@
                     msg.setAsynchronous(true);
                     msg.sendToTarget();
                 }
-                return key_consumed;
+                return true;
             case KeyEvent.KEYCODE_NOTIFICATION:
                 if (!down) {
                     toggleNotificationPanel();
                 }
-                return key_consumed;
+                return true;
             case KeyEvent.KEYCODE_SEARCH:
-                if (down && repeatCount == 0 && !keyguardOn()) {
-                    switch(mSearchKeyBehavior) {
+                if (firstDown && !keyguardOn) {
+                    switch (mSearchKeyBehavior) {
                         case SEARCH_BEHAVIOR_TARGET_ACTIVITY: {
                             launchTargetSearchActivity();
-                            return key_consumed;
+                            return true;
                         }
                         case SEARCH_BEHAVIOR_DEFAULT_SEARCH:
                         default:
@@ -3247,21 +3292,18 @@
                 }
                 break;
             case KeyEvent.KEYCODE_LANGUAGE_SWITCH:
-                if (down && repeatCount == 0) {
+                if (firstDown) {
                     int direction = (metaState & KeyEvent.META_SHIFT_MASK) != 0 ? -1 : 1;
                     sendSwitchKeyboardLayout(event, direction);
-                    return key_consumed;
+                    return true;
                 }
                 break;
             case KeyEvent.KEYCODE_SPACE:
                 // Handle keyboard layout switching. (META + SPACE)
-                if ((metaState & KeyEvent.META_META_MASK) == 0) {
-                    return key_not_consumed;
-                }
-                if (down && repeatCount == 0) {
+                if (firstDown && event.isMetaPressed()) {
                     int direction = (metaState & KeyEvent.META_SHIFT_MASK) != 0 ? -1 : 1;
                     sendSwitchKeyboardLayout(event, direction);
-                    return key_consumed;
+                    return true;
                 }
                 break;
             case KeyEvent.KEYCODE_META_LEFT:
@@ -3286,7 +3328,7 @@
                         mPendingMetaAction = false;
                     }
                 }
-                return key_consumed;
+                return true;
             case KeyEvent.KEYCODE_ALT_LEFT:
             case KeyEvent.KEYCODE_ALT_RIGHT:
                 if (down) {
@@ -3302,14 +3344,14 @@
                             && (metaState & mRecentAppsHeldModifiers) == 0) {
                         mRecentAppsHeldModifiers = 0;
                         hideRecentApps(true, false);
-                        return key_consumed;
+                        return true;
                     }
 
                     // Toggle Caps Lock on META-ALT.
                     if (mPendingCapsLockToggle) {
                         mInputManagerInternal.toggleCapsLock(event.getDeviceId());
                         mPendingCapsLockToggle = false;
-                        return key_consumed;
+                        return true;
                     }
                 }
                 break;
@@ -3319,24 +3361,18 @@
             case KeyEvent.KEYCODE_STYLUS_BUTTON_TAIL:
                 Slog.wtf(TAG, "KEYCODE_STYLUS_BUTTON_* should be handled in"
                         + " interceptKeyBeforeQueueing");
-                return key_consumed;
+                return true;
         }
-
         if (isValidGlobalKey(keyCode)
                 && mGlobalKeyManager.handleGlobalKey(mContext, keyCode, event)) {
-            return key_consumed;
+            return true;
         }
 
         // Reserve all the META modifier combos for system behavior
-        if ((metaState & KeyEvent.META_META_ON) != 0) {
-            return key_consumed;
-        }
-
-        // Let the application handle the key.
-        return key_not_consumed;
+        return (metaState & KeyEvent.META_META_ON) != 0;
     }
 
-    private int handleHomeShortcuts(int displayId, IBinder focusedToken, KeyEvent event) {
+    private boolean handleHomeShortcuts(int displayId, IBinder focusedToken, KeyEvent event) {
         // First we always handle the home key here, so applications
         // can never break it, although if keyguard is on, we do let
         // it handle it, because that gives us the correct 5 second
@@ -3618,19 +3654,17 @@
     }
 
     @Override
-    public void onKeyguardOccludedChangedLw(boolean occluded, boolean waitAppTransition) {
-        if (mKeyguardDelegate != null && waitAppTransition) {
+    public void onKeyguardOccludedChangedLw(boolean occluded) {
+        if (mKeyguardDelegate != null) {
             mPendingKeyguardOccluded = occluded;
             mKeyguardOccludedChanged = true;
-        } else {
-            setKeyguardOccludedLw(occluded);
         }
     }
 
     @Override
     public int applyKeyguardOcclusionChange() {
         if (DEBUG_KEYGUARD) Slog.d(TAG, "transition/occluded commit occluded="
-                + mPendingKeyguardOccluded);
+                + mPendingKeyguardOccluded + " changed=" + mKeyguardOccludedChanged);
 
         // TODO(b/276433230): Explicitly save before/after for occlude state in each
         // Transition so we don't need to update SysUI every time.
@@ -5046,15 +5080,10 @@
         // ... eventually calls finishWindowsDrawn which will finalize our screen turn on
         // as well as enabling the orientation change logic/sensor.
         Trace.asyncTraceBegin(Trace.TRACE_TAG_WINDOW_MANAGER,
-                TRACE_WAIT_FOR_ALL_WINDOWS_DRAWN_METHOD, /* cookie= */ 0);
-        mWindowManagerInternal.waitForAllWindowsDrawn(() -> {
-            if (DEBUG_WAKEUP) Slog.i(TAG, "All windows ready for every display");
-            mHandler.sendMessage(mHandler.obtainMessage(MSG_WINDOW_MANAGER_DRAWN_COMPLETE,
-                    INVALID_DISPLAY, 0));
-
-            Trace.asyncTraceEnd(Trace.TRACE_TAG_WINDOW_MANAGER,
-                    TRACE_WAIT_FOR_ALL_WINDOWS_DRAWN_METHOD, /* cookie= */ 0);
-            }, WAITING_FOR_DRAWN_TIMEOUT, INVALID_DISPLAY);
+                TRACE_WAIT_FOR_ALL_WINDOWS_DRAWN_METHOD, INVALID_DISPLAY /* cookie */);
+        mWindowManagerInternal.waitForAllWindowsDrawn(mHandler.obtainMessage(
+                MSG_WINDOW_MANAGER_DRAWN_COMPLETE, INVALID_DISPLAY, 0),
+                WAITING_FOR_DRAWN_TIMEOUT, INVALID_DISPLAY);
     }
 
     // Called on the DisplayManager's DisplayPowerController thread.
@@ -5134,15 +5163,10 @@
             mScreenOnListeners.put(displayId, screenOnListener);
 
             Trace.asyncTraceBegin(Trace.TRACE_TAG_WINDOW_MANAGER,
-                    TRACE_WAIT_FOR_ALL_WINDOWS_DRAWN_METHOD, /* cookie= */ 0);
-            mWindowManagerInternal.waitForAllWindowsDrawn(() -> {
-                if (DEBUG_WAKEUP) Slog.i(TAG, "All windows ready for display: " + displayId);
-                mHandler.sendMessage(mHandler.obtainMessage(MSG_WINDOW_MANAGER_DRAWN_COMPLETE,
-                        displayId, 0));
-
-                Trace.asyncTraceEnd(Trace.TRACE_TAG_WINDOW_MANAGER,
-                        TRACE_WAIT_FOR_ALL_WINDOWS_DRAWN_METHOD, /* cookie= */ 0);
-            }, WAITING_FOR_DRAWN_TIMEOUT, displayId);
+                    TRACE_WAIT_FOR_ALL_WINDOWS_DRAWN_METHOD, displayId /* cookie */);
+            mWindowManagerInternal.waitForAllWindowsDrawn(mHandler.obtainMessage(
+                    MSG_WINDOW_MANAGER_DRAWN_COMPLETE, displayId, 0),
+                    WAITING_FOR_DRAWN_TIMEOUT, displayId);
         }
     }
 
diff --git a/services/core/java/com/android/server/policy/SingleKeyGestureDetector.java b/services/core/java/com/android/server/policy/SingleKeyGestureDetector.java
index 9c3b38a..b999bbb3 100644
--- a/services/core/java/com/android/server/policy/SingleKeyGestureDetector.java
+++ b/services/core/java/com/android/server/policy/SingleKeyGestureDetector.java
@@ -314,7 +314,9 @@
             if (eventTime < mLastDownTime + mActiveRule.getVeryLongPressTimeoutMs()) {
                 mHandler.removeMessages(MSG_KEY_VERY_LONG_PRESS);
             } else {
-                mHandledByLongPress = mActiveRule.supportVeryLongPress();
+                // If long press or very long press (~3.5s) had been handled, we should skip the
+                // short press behavior.
+                mHandledByLongPress |= mActiveRule.supportVeryLongPress();
             }
         }
 
diff --git a/services/core/java/com/android/server/policy/WindowManagerPolicy.java b/services/core/java/com/android/server/policy/WindowManagerPolicy.java
index 887f946..03a7bd3 100644
--- a/services/core/java/com/android/server/policy/WindowManagerPolicy.java
+++ b/services/core/java/com/android/server/policy/WindowManagerPolicy.java
@@ -169,7 +169,7 @@
      *
      * @param occluded Whether Keyguard is currently occluded or not.
      */
-    void onKeyguardOccludedChangedLw(boolean occluded, boolean waitAppTransition);
+    void onKeyguardOccludedChangedLw(boolean occluded);
 
     /**
      * Commit any queued changes to keyguard occlude status that had been deferred during the
diff --git a/services/core/java/com/android/server/power/TEST_MAPPING b/services/core/java/com/android/server/power/TEST_MAPPING
index cf1bfc3..fbfe291 100644
--- a/services/core/java/com/android/server/power/TEST_MAPPING
+++ b/services/core/java/com/android/server/power/TEST_MAPPING
@@ -20,6 +20,7 @@
       "name": "FrameworksServicesTests",
       "options": [
         {"include-filter": "com.android.server.power"},
+        {"exclude-filter": "com.android.server.power.BatteryStatsTests"},
         {"exclude-annotation": "android.platform.test.annotations.FlakyTest"},
         {"exclude-annotation": "androidx.test.filters.FlakyTest"}
       ]
@@ -38,7 +39,8 @@
     {
       "name": "FrameworksServicesTests",
       "options": [
-        {"include-filter": "com.android.server.power"}
+        {"include-filter": "com.android.server.power"},
+        {"exclude-filter": "com.android.server.power.BatteryStatsTests"}
       ]
     }
   ]
diff --git a/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java b/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java
index 4a57592a..27329e2 100644
--- a/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java
+++ b/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java
@@ -14613,17 +14613,13 @@
 
     // Inform StatsLog of setBatteryState changes.
     private void reportChangesToStatsLog(final int status, final int plugType, final int level) {
-        if (!mHaveBatteryLevel) {
-            return;
-        }
-
-        if (mBatteryStatus != status) {
+        if (!mHaveBatteryLevel || mBatteryStatus != status) {
             FrameworkStatsLog.write(FrameworkStatsLog.CHARGING_STATE_CHANGED, status);
         }
-        if (mBatteryPlugType != plugType) {
+        if (!mHaveBatteryLevel || mBatteryPlugType != plugType) {
             FrameworkStatsLog.write(FrameworkStatsLog.PLUGGED_STATE_CHANGED, plugType);
         }
-        if (mBatteryLevel != level) {
+        if (!mHaveBatteryLevel || mBatteryLevel != level) {
             FrameworkStatsLog.write(FrameworkStatsLog.BATTERY_LEVEL_CHANGED, level);
         }
     }
diff --git a/services/core/java/com/android/server/utils/AlarmQueue.java b/services/core/java/com/android/server/utils/AlarmQueue.java
index 09ba195..83605ae 100644
--- a/services/core/java/com/android/server/utils/AlarmQueue.java
+++ b/services/core/java/com/android/server/utils/AlarmQueue.java
@@ -151,6 +151,10 @@
     @GuardedBy("mLock")
     @ElapsedRealtimeLong
     private long mTriggerTimeElapsed = NOT_SCHEDULED;
+    /** The last time an alarm went off (ie. the last time {@link #onAlarm()} was called}). */
+    @GuardedBy("mLock")
+    @ElapsedRealtimeLong
+    private long mLastFireTimeElapsed;
 
     /**
      * @param alarmTag               The tag to use when scheduling the alarm with AlarmManager.
@@ -284,7 +288,7 @@
     /** Sets an alarm with {@link AlarmManager} for the earliest alarm in the queue after now. */
     @GuardedBy("mLock")
     private void setNextAlarmLocked() {
-        setNextAlarmLocked(mInjector.getElapsedRealtime());
+        setNextAlarmLocked(mLastFireTimeElapsed + mMinTimeBetweenAlarmsMs);
     }
 
     /**
@@ -334,6 +338,7 @@
         final ArraySet<K> expired = new ArraySet<>();
         synchronized (mLock) {
             final long nowElapsed = mInjector.getElapsedRealtime();
+            mLastFireTimeElapsed = nowElapsed;
             while (mAlarmPriorityQueue.size() > 0) {
                 final Pair<K, Long> alarm = mAlarmPriorityQueue.peek();
                 if (alarm.second <= nowElapsed) {
diff --git a/services/core/java/com/android/server/vr/VrManagerService.java b/services/core/java/com/android/server/vr/VrManagerService.java
index b296ef2..1ff01a6 100644
--- a/services/core/java/com/android/server/vr/VrManagerService.java
+++ b/services/core/java/com/android/server/vr/VrManagerService.java
@@ -1049,7 +1049,11 @@
 
         for (ComponentName c : possibleServices) {
             if (Objects.equals(c.getPackageName(), pkg)) {
-                nm.setNotificationListenerAccessGrantedForUser(c, userId, true);
+                try {
+                    nm.setNotificationListenerAccessGrantedForUser(c, userId, true);
+                } catch (Exception e) {
+                    Slog.w(TAG, "Could not grant NLS access to package " + pkg, e);
+                }
             }
         }
     }
diff --git a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
index 9b2cdd7..ee7dc50 100644
--- a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
+++ b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
@@ -3113,7 +3113,10 @@
             if (which == FLAG_SYSTEM && systemIsStatic && systemIsBoth) {
                 Slog.i(TAG, "Migrating current wallpaper to be lock-only before"
                         + " updating system wallpaper");
-                migrateStaticSystemToLockWallpaperLocked(userId);
+                if (!migrateStaticSystemToLockWallpaperLocked(userId)
+                        && !isLockscreenLiveWallpaperEnabled()) {
+                    which |= FLAG_LOCK;
+                }
             }
 
             wallpaper = getWallpaperSafeLocked(userId, which);
@@ -3141,13 +3144,13 @@
         }
     }
 
-    private void migrateStaticSystemToLockWallpaperLocked(int userId) {
+    private boolean migrateStaticSystemToLockWallpaperLocked(int userId) {
         WallpaperData sysWP = mWallpaperMap.get(userId);
         if (sysWP == null) {
             if (DEBUG) {
                 Slog.i(TAG, "No system wallpaper?  Not tracking for lock-only");
             }
-            return;
+            return true;
         }
 
         // We know a-priori that there is no lock-only wallpaper currently
@@ -3174,9 +3177,12 @@
                 SELinux.restorecon(lockWP.getWallpaperFile());
                 mLastLockWallpaper = lockWP;
             }
+            return true;
         } catch (ErrnoException e) {
-            Slog.e(TAG, "Can't migrate system wallpaper: " + e.getMessage());
+            // can happen when migrating default wallpaper (which is not stored in wallpaperFile)
+            Slog.w(TAG, "Couldn't migrate system wallpaper: " + e.getMessage());
             clearWallpaperBitmaps(lockWP);
+            return false;
         }
     }
 
@@ -3388,7 +3394,9 @@
                     // therefore it's a shared system+lock image that we need to migrate.
                     Slog.i(TAG, "Migrating current wallpaper to be lock-only before"
                             + "updating system wallpaper");
-                    migrateStaticSystemToLockWallpaperLocked(userId);
+                    if (!migrateStaticSystemToLockWallpaperLocked(userId)) {
+                        which |= FLAG_LOCK;
+                    }
                 }
             }
 
diff --git a/services/core/java/com/android/server/wm/AccessibilityController.java b/services/core/java/com/android/server/wm/AccessibilityController.java
index f300113..b3ae2ee 100644
--- a/services/core/java/com/android/server/wm/AccessibilityController.java
+++ b/services/core/java/com/android/server/wm/AccessibilityController.java
@@ -1280,45 +1280,53 @@
                 }
 
                 void drawIfNeeded(SurfaceControl.Transaction t) {
+                    // Drawing variables (alpha, dirty rect, and bounds) access is synchronized
+                    // using WindowManagerGlobalLock. Grab copies of these values before
+                    // drawing on the canvas so that drawing can be performed outside of the lock.
+                    int alpha;
+                    Rect drawingRect = null;
+                    Region drawingBounds = null;
                     synchronized (mService.mGlobalLock) {
                         if (!mInvalidated) {
                             return;
                         }
                         mInvalidated = false;
-                        if (mAlpha > 0) {
-                            Canvas canvas = null;
-                            try {
-                                // Empty dirty rectangle means unspecified.
-                                if (mDirtyRect.isEmpty()) {
-                                    mBounds.getBounds(mDirtyRect);
-                                }
-                                mDirtyRect.inset(-mHalfBorderWidth, -mHalfBorderWidth);
-                                canvas = mSurface.lockCanvas(mDirtyRect);
-                                if (DEBUG_VIEWPORT_WINDOW) {
-                                    Slog.i(LOG_TAG, "Dirty rect: " + mDirtyRect);
-                                }
-                            } catch (IllegalArgumentException iae) {
-                                /* ignore */
-                            } catch (Surface.OutOfResourcesException oore) {
-                                /* ignore */
-                            }
-                            if (canvas == null) {
-                                return;
-                            }
-                            if (DEBUG_VIEWPORT_WINDOW) {
-                                Slog.i(LOG_TAG, "Bounds: " + mBounds);
-                            }
-                            canvas.drawColor(Color.TRANSPARENT, Mode.CLEAR);
-                            mPaint.setAlpha(mAlpha);
-                            Path path = mBounds.getBoundaryPath();
-                            canvas.drawPath(path, mPaint);
 
-                            mSurface.unlockCanvasAndPost(canvas);
-                            t.show(mSurfaceControl);
-                        } else {
-                            t.hide(mSurfaceControl);
+                        alpha = mAlpha;
+                        if (alpha > 0) {
+                            drawingBounds = new Region(mBounds);
+                            // Empty dirty rectangle means unspecified.
+                            if (mDirtyRect.isEmpty()) {
+                                mBounds.getBounds(mDirtyRect);
+                            }
+                            mDirtyRect.inset(-mHalfBorderWidth, -mHalfBorderWidth);
+                            drawingRect = new Rect(mDirtyRect);
+                            if (DEBUG_VIEWPORT_WINDOW) {
+                                Slog.i(LOG_TAG, "ViewportWindow bounds: " + mBounds);
+                                Slog.i(LOG_TAG, "ViewportWindow dirty rect: " + mDirtyRect);
+                            }
                         }
                     }
+
+                    // Draw without holding WindowManagerGlobalLock.
+                    if (alpha > 0) {
+                        Canvas canvas = null;
+                        try {
+                            canvas = mSurface.lockCanvas(drawingRect);
+                        } catch (IllegalArgumentException | OutOfResourcesException e) {
+                            /* ignore */
+                        }
+                        if (canvas == null) {
+                            return;
+                        }
+                        canvas.drawColor(Color.TRANSPARENT, Mode.CLEAR);
+                        mPaint.setAlpha(alpha);
+                        canvas.drawPath(drawingBounds.getBoundaryPath(), mPaint);
+                        mSurface.unlockCanvasAndPost(canvas);
+                        t.show(mSurfaceControl);
+                    } else {
+                        t.hide(mSurfaceControl);
+                    }
                 }
 
                 void releaseSurface() {
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java b/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java
index 32f1f42..a2547fd 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java
@@ -23,6 +23,7 @@
 import android.app.AppProtoEnums;
 import android.app.BackgroundStartPrivileges;
 import android.app.IActivityManager;
+import android.app.IAppTask;
 import android.app.IApplicationThread;
 import android.app.ITaskStackListener;
 import android.app.ProfilerInfo;
@@ -308,6 +309,12 @@
     public abstract void notifyActiveDreamChanged(@Nullable ComponentName activeDreamComponent);
 
     /**
+     * Starts a dream activity in the DreamService's process.
+     */
+    public abstract IAppTask startDreamActivity(@NonNull Intent intent, int callingUid,
+            int callingPid);
+
+    /**
      * Set a uid that is allowed to bypass stopped app switches, launching an app
      * whenever it wants.
      *
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index 10ff3a3..78da5de 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -1467,23 +1467,8 @@
         return false;
     }
 
-    private void enforceCallerIsDream(String callerPackageName) {
-        final long origId = Binder.clearCallingIdentity();
-        try {
-            if (!canLaunchDreamActivity(callerPackageName)) {
-                throw new SecurityException("The dream activity can be started only when the device"
-                        + " is dreaming and only by the active dream package.");
-            }
-        } finally {
-            Binder.restoreCallingIdentity(origId);
-        }
-    }
-
-    @Override
-    public boolean startDreamActivity(@NonNull Intent intent) {
-        assertPackageMatchesCallingUid(intent.getPackage());
-        enforceCallerIsDream(intent.getPackage());
-
+    private IAppTask startDreamActivityInternal(@NonNull Intent intent, int callingUid,
+            int callingPid) {
         final ActivityInfo a = new ActivityInfo();
         a.theme = com.android.internal.R.style.Theme_Dream;
         a.exported = true;
@@ -1501,7 +1486,7 @@
         options.setLaunchActivityType(ACTIVITY_TYPE_DREAM);
 
         synchronized (mGlobalLock) {
-            final WindowProcessController process = mProcessMap.getProcess(Binder.getCallingPid());
+            final WindowProcessController process = mProcessMap.getProcess(callingPid);
 
             a.packageName = process.mInfo.packageName;
             a.applicationInfo = process.mInfo;
@@ -1509,26 +1494,25 @@
             a.uiOptions = process.mInfo.uiOptions;
             a.taskAffinity = "android:" + a.packageName + "/dream";
 
-            final int callingUid = Binder.getCallingUid();
-            final int callingPid = Binder.getCallingPid();
 
-            final long origId = Binder.clearCallingIdentity();
-            try {
-                getActivityStartController().obtainStarter(intent, "dream")
-                        .setCallingUid(callingUid)
-                        .setCallingPid(callingPid)
-                        .setCallingPackage(intent.getPackage())
-                        .setActivityInfo(a)
-                        .setActivityOptions(createSafeActivityOptionsWithBalAllowed(options))
-                        // To start the dream from background, we need to start it from a persistent
-                        // system process. Here we set the real calling uid to the system server uid
-                        .setRealCallingUid(Binder.getCallingUid())
-                        .setBackgroundStartPrivileges(BackgroundStartPrivileges.ALLOW_BAL)
-                        .execute();
-                return true;
-            } finally {
-                Binder.restoreCallingIdentity(origId);
-            }
+            final ActivityRecord[] outActivity = new ActivityRecord[1];
+            getActivityStartController().obtainStarter(intent, "dream")
+                    .setCallingUid(callingUid)
+                    .setCallingPid(callingPid)
+                    .setCallingPackage(intent.getPackage())
+                    .setActivityInfo(a)
+                    .setActivityOptions(createSafeActivityOptionsWithBalAllowed(options))
+                    .setOutActivity(outActivity)
+                    // To start the dream from background, we need to start it from a persistent
+                    // system process. Here we set the real calling uid to the system server uid
+                    .setRealCallingUid(Binder.getCallingUid())
+                    .setBackgroundStartPrivileges(BackgroundStartPrivileges.ALLOW_BAL)
+                    .execute();
+
+            final ActivityRecord started = outActivity[0];
+            final IAppTask appTask = started == null ? null :
+                    new AppTaskImpl(this, started.getTask().mTaskId, callingUid);
+            return appTask;
         }
     }
 
@@ -5926,6 +5910,11 @@
         }
 
         @Override
+        public IAppTask startDreamActivity(@NonNull Intent intent, int callingUid, int callingPid) {
+            return startDreamActivityInternal(intent, callingUid, callingPid);
+        }
+
+        @Override
         public void setAllowAppSwitches(@NonNull String type, int uid, int userId) {
             if (!mAmInternal.isUserRunning(userId, ActivityManager.FLAG_OR_STOPPED)) {
                 return;
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index 12b5f5f..bfd2a10 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -3701,6 +3701,7 @@
         mInputMonitor.dump(pw, "  ");
         pw.println();
         mInsetsStateController.dump(prefix, pw);
+        mInsetsPolicy.dump(prefix, pw);
         mDwpcHelper.dump(prefix, pw);
         pw.println();
     }
diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java
index a02fd11..2717a6a 100644
--- a/services/core/java/com/android/server/wm/DisplayPolicy.java
+++ b/services/core/java/com/android/server/wm/DisplayPolicy.java
@@ -327,8 +327,6 @@
     private WindowState mTopFullscreenOpaqueWindowState;
     private boolean mTopIsFullscreen;
     private int mNavBarOpacityMode = NAV_BAR_OPAQUE_WHEN_FREEFORM_OR_DOCKED;
-    private boolean mForceConsumeSystemBars;
-    private boolean mForceShowSystemBars;
 
     /**
      * Windows that provides gesture insets. If multiple windows provide gesture insets at the same
@@ -1286,18 +1284,10 @@
         return ANIMATION_STYLEABLE;
     }
 
-    /**
-     * @return true if the system bars are forced to be consumed
-     */
+    // TODO (b/277891341): Remove this and related usages. This has been replaced by
+    //                     InsetsSource#FLAG_FORCE_CONSUMING.
     public boolean areSystemBarsForcedConsumedLw() {
-        return mForceConsumeSystemBars;
-    }
-
-    /**
-     * @return true if the system bars are forced to stay visible
-     */
-    public boolean areSystemBarsForcedShownLw() {
-        return mForceShowSystemBars;
+        return false;
     }
 
     /**
@@ -1694,7 +1684,8 @@
      * @return Whether the top fullscreen app hides the given type of system bar.
      */
     boolean topAppHidesSystemBar(@InsetsType int type) {
-        if (mTopFullscreenOpaqueWindowState == null || mForceShowSystemBars) {
+        if (mTopFullscreenOpaqueWindowState == null
+                || getInsetsPolicy().areTypesForciblyShowing(type)) {
             return false;
         }
         return !mTopFullscreenOpaqueWindowState.isRequestedVisible(type);
@@ -2371,14 +2362,7 @@
         final boolean freeformRootTaskVisible =
                 defaultTaskDisplayArea.isRootTaskVisible(WINDOWING_MODE_FREEFORM);
 
-        // We need to force showing system bars when adjacent tasks or freeform roots visible.
-        mForceShowSystemBars = adjacentTasksVisible || freeformRootTaskVisible;
-        // We need to force the consumption of the system bars if they are force shown or if they
-        // are controlled by a remote insets controller.
-        mForceConsumeSystemBars = mForceShowSystemBars
-                || getInsetsPolicy().remoteInsetsControllerControlsSystemBars(win)
-                || getInsetsPolicy().forcesShowingNavigationBars(win);
-        mDisplayContent.getInsetsPolicy().updateBarControlTarget(win);
+        getInsetsPolicy().updateSystemBars(win, adjacentTasksVisible, freeformRootTaskVisible);
 
         final boolean topAppHidesStatusBar = topAppHidesSystemBar(Type.statusBars());
         if (getStatusBar() != null) {
diff --git a/services/core/java/com/android/server/wm/InputManagerCallback.java b/services/core/java/com/android/server/wm/InputManagerCallback.java
index 0a47fe0..20595ea 100644
--- a/services/core/java/com/android/server/wm/InputManagerCallback.java
+++ b/services/core/java/com/android/server/wm/InputManagerCallback.java
@@ -128,6 +128,21 @@
         }
     }
 
+    /** Notifies that the pointer location configuration has changed. */
+    @Override
+    public void notifyPointerLocationChanged(boolean pointerLocationEnabled) {
+        if (mService.mPointerLocationEnabled == pointerLocationEnabled) {
+            return;
+        }
+
+        synchronized (mService.mGlobalLock) {
+            mService.mPointerLocationEnabled = pointerLocationEnabled;
+            mService.mRoot.forAllDisplayPolicies(
+                    p -> p.setPointerLocationEnabled(mService.mPointerLocationEnabled)
+            );
+        }
+    }
+
     /** Notifies that the lid switch changed state. */
     @Override
     public void notifyLidSwitchChanged(long whenNanos, boolean lidOpen) {
diff --git a/services/core/java/com/android/server/wm/InsetsPolicy.java b/services/core/java/com/android/server/wm/InsetsPolicy.java
index 798dc85..835c92d 100644
--- a/services/core/java/com/android/server/wm/InsetsPolicy.java
+++ b/services/core/java/com/android/server/wm/InsetsPolicy.java
@@ -21,12 +21,7 @@
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
 import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
 import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
-import static android.view.InsetsController.ANIMATION_TYPE_HIDE;
-import static android.view.InsetsController.ANIMATION_TYPE_SHOW;
-import static android.view.InsetsController.LAYOUT_INSETS_DURING_ANIMATION_HIDDEN;
-import static android.view.InsetsController.LAYOUT_INSETS_DURING_ANIMATION_SHOWN;
 import static android.view.InsetsSource.ID_IME;
-import static android.view.SyncRtSurfaceTransactionApplier.applyParams;
 import static android.view.WindowInsetsController.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE;
 import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_FORCE_SHOW_STATUS_BAR;
 import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_STATUS_FORCE_SHOW_NAVIGATION;
@@ -39,16 +34,13 @@
 import android.app.WindowConfiguration;
 import android.content.ComponentName;
 import android.content.res.Resources;
+import android.os.Handler;
+import android.os.IBinder;
 import android.util.SparseArray;
-import android.view.InsetsAnimationControlCallbacks;
-import android.view.InsetsAnimationControlImpl;
-import android.view.InsetsAnimationControlRunner;
 import android.view.InsetsController;
 import android.view.InsetsFrameProvider;
 import android.view.InsetsSource;
-import android.view.InsetsSourceControl;
 import android.view.InsetsState;
-import android.view.InternalInsetsAnimationController;
 import android.view.SurfaceControl;
 import android.view.SyncRtSurfaceTransactionApplier;
 import android.view.WindowInsets;
@@ -56,14 +48,16 @@
 import android.view.WindowInsets.Type.InsetsType;
 import android.view.WindowInsetsAnimation;
 import android.view.WindowInsetsAnimation.Bounds;
-import android.view.WindowInsetsAnimationControlListener;
 import android.view.WindowManager;
+import android.view.inputmethod.InputMethodManager;
 
 import com.android.internal.R;
 import com.android.internal.annotations.VisibleForTesting;
-import com.android.server.DisplayThread;
 import com.android.server.statusbar.StatusBarManagerInternal;
 
+import java.io.PrintWriter;
+import java.util.List;
+
 /**
  * Policy that implements who gets control over the windows generating insets.
  */
@@ -77,47 +71,19 @@
     private final DisplayContent mDisplayContent;
     private final DisplayPolicy mPolicy;
 
-    /** For resetting visibilities of insets sources. */
-    private final InsetsControlTarget mDummyControlTarget = new InsetsControlTarget() {
+    /** Used to show system bars transiently. This won't affect the layout. */
+    private final InsetsControlTarget mTransientControlTarget;
 
-        @Override
-        public void notifyInsetsControlChanged() {
-            boolean hasLeash = false;
-            final InsetsSourceControl[] controls =
-                    mStateController.getControlsForDispatch(this);
-            if (controls == null) {
-                return;
-            }
-            for (InsetsSourceControl control : controls) {
-                if (isTransient(control.getType())) {
-                    // The visibilities of transient bars will be handled with animations.
-                    continue;
-                }
-                final SurfaceControl leash = control.getLeash();
-                if (leash != null) {
-                    hasLeash = true;
-
-                    // We use alpha to control the visibility here which aligns the logic at
-                    // SurfaceAnimator.createAnimationLeash
-                    final boolean visible =
-                            (control.getType() & WindowInsets.Type.defaultVisible()) != 0;
-                    mDisplayContent.getPendingTransaction().setAlpha(leash, visible ? 1f : 0f);
-                }
-            }
-            if (hasLeash) {
-                mDisplayContent.scheduleAnimation();
-            }
-        }
-    };
+    /** Used to show system bars permanently. This will affect the layout. */
+    private final InsetsControlTarget mPermanentControlTarget;
 
     private WindowState mFocusedWin;
     private final BarWindow mStatusBar = new BarWindow(StatusBarManager.WINDOW_STATUS_BAR);
     private final BarWindow mNavBar = new BarWindow(StatusBarManager.WINDOW_NAVIGATION_BAR);
     private @InsetsType int mShowingTransientTypes;
-    private boolean mAnimatingShown;
+    private @InsetsType int mForcedShowingTypes;
 
     private final boolean mHideNavBarForKeyboard;
-    private final float[] mTmpFloat9 = new float[9];
 
     InsetsPolicy(InsetsStateController stateController, DisplayContent displayContent) {
         mStateController = stateController;
@@ -125,9 +91,12 @@
         mPolicy = displayContent.getDisplayPolicy();
         final Resources r = mPolicy.getContext().getResources();
         mHideNavBarForKeyboard = r.getBoolean(R.bool.config_hideNavBarForKeyboard);
+        mTransientControlTarget = new ControlTarget(
+                stateController, displayContent.mWmService.mH, "TransientControlTarget");
+        mPermanentControlTarget = new ControlTarget(
+                stateController, displayContent.mWmService.mH, "PermanentControlTarget");
     }
 
-
     /** Updates the target which can control system bars. */
     void updateBarControlTarget(@Nullable WindowState focusedWin) {
         if (mFocusedWin != focusedWin) {
@@ -142,13 +111,13 @@
         final WindowState topApp = mPolicy.getTopFullscreenOpaqueWindow();
         mStateController.onBarControlTargetChanged(
                 statusControlTarget,
-                statusControlTarget == mDummyControlTarget
+                statusControlTarget == mTransientControlTarget
                         ? getStatusControlTarget(focusedWin, true /* fake */)
                         : statusControlTarget == notificationShade
                                 ? getStatusControlTarget(topApp, true /* fake */)
                                 : null,
                 navControlTarget,
-                navControlTarget == mDummyControlTarget
+                navControlTarget == mTransientControlTarget
                         ? getNavControlTarget(focusedWin, true /* fake */)
                         : navControlTarget == notificationShade
                                 ? getNavControlTarget(topApp, true /* fake */)
@@ -198,19 +167,19 @@
                     mFocusedWin,
                     (showingTransientTypes & (Type.statusBars() | Type.navigationBars())) != 0,
                     isGestureOnSystemBar);
-
-            // The leashes can be created while updating bar control target. The surface transaction
-            // of the new leashes might not be applied yet. The callback posted here ensures we can
-            // get the valid leashes because the surface transaction will be applied in the next
-            // animation frame which will be triggered if a new leash is created.
-            mDisplayContent.mWmService.mAnimator.getChoreographer().postFrameCallback(time -> {
-                synchronized (mDisplayContent.mWmService.mGlobalLock) {
-                    startAnimation(true /* show */, null /* callback */);
-                }
-            });
         }
     }
 
+    @VisibleForTesting
+    InsetsControlTarget getTransientControlTarget() {
+        return  mTransientControlTarget;
+    }
+
+    @VisibleForTesting
+    InsetsControlTarget getPermanentControlTarget() {
+        return  mPermanentControlTarget;
+    }
+
     void hideTransient() {
         if (mShowingTransientTypes == 0) {
             return;
@@ -221,23 +190,8 @@
                 /* areVisible= */ false,
                 /* wereRevealedFromSwipeOnSystemBar= */ false);
 
-        startAnimation(false /* show */, () -> {
-            synchronized (mDisplayContent.mWmService.mGlobalLock) {
-                final SparseArray<InsetsSourceProvider> providers =
-                        mStateController.getSourceProviders();
-                for (int i = providers.size() - 1; i >= 0; i--) {
-                    final InsetsSourceProvider provider = providers.valueAt(i);
-                    if (!isTransient(provider.getSource().getType())) {
-                        continue;
-                    }
-                    // We are about to clear mShowingTransientTypes, we don't want the transient bar
-                    // can cause insets on the client. Restore the client visibility.
-                    provider.setClientVisible(false);
-                }
-                mShowingTransientTypes = 0;
-                updateBarControlTarget(mFocusedWin);
-            }
-        });
+        mShowingTransientTypes = 0;
+        updateBarControlTarget(mFocusedWin);
     }
 
     boolean isTransient(@InsetsType int type) {
@@ -500,7 +454,7 @@
     private @Nullable InsetsControlTarget getStatusControlTarget(@Nullable WindowState focusedWin,
             boolean fake) {
         if (!fake && isTransient(Type.statusBars())) {
-            return mDummyControlTarget;
+            return mTransientControlTarget;
         }
         final WindowState notificationShade = mPolicy.getNotificationShade();
         if (focusedWin == notificationShade) {
@@ -514,16 +468,16 @@
                     component, focusedWin.getRequestedVisibleTypes());
             return mDisplayContent.mRemoteInsetsControlTarget;
         }
-        if (mPolicy.areSystemBarsForcedShownLw()) {
+        if (areTypesForciblyShowing(Type.statusBars())) {
             // Status bar is forcibly shown. We don't want the client to control the status bar, and
             // we will dispatch the real visibility of status bar to the client.
-            return null;
+            return mPermanentControlTarget;
         }
         if (forceShowsStatusBarTransiently() && !fake) {
             // Status bar is forcibly shown transiently, and its new visibility won't be
             // dispatched to the client so that we can keep the layout stable. We will dispatch the
             // fake control to the client, so that it can re-show the bar during this scenario.
-            return mDummyControlTarget;
+            return mTransientControlTarget;
         }
         if (!canBeTopFullscreenOpaqueWindow(focusedWin)
                 && mPolicy.topAppHidesSystemBar(Type.statusBars())
@@ -554,7 +508,7 @@
             return null;
         }
         if (!fake && isTransient(Type.navigationBars())) {
-            return mDummyControlTarget;
+            return mTransientControlTarget;
         }
         if (focusedWin == mPolicy.getNotificationShade()) {
             // Notification shade has control anyways, no reason to force anything.
@@ -567,13 +521,6 @@
                 return focusedWin;
             }
         }
-        if (forcesShowingNavigationBars(focusedWin)) {
-            // When "force show navigation bar" is enabled, it means both force visible is true, and
-            // we are in 3-button navigation. In this mode, the navigation bar is forcibly shown
-            // when activity type is ACTIVITY_TYPE_STANDARD which means Launcher or Recent could
-            // still control the navigation bar in this mode.
-            return null;
-        }
         if (remoteInsetsControllerControlsSystemBars(focusedWin)) {
             ComponentName component = focusedWin.mActivityRecord != null
                     ? focusedWin.mActivityRecord.mActivityComponent : null;
@@ -581,16 +528,16 @@
                     component, focusedWin.getRequestedVisibleTypes());
             return mDisplayContent.mRemoteInsetsControlTarget;
         }
-        if (mPolicy.areSystemBarsForcedShownLw()) {
+        if (areTypesForciblyShowing(Type.navigationBars())) {
             // Navigation bar is forcibly shown. We don't want the client to control the navigation
             // bar, and we will dispatch the real visibility of navigation bar to the client.
-            return null;
+            return mPermanentControlTarget;
         }
         if (forceShowsNavigationBarTransiently() && !fake) {
             // Navigation bar is forcibly shown transiently, and its new visibility won't be
             // dispatched to the client so that we can keep the layout stable. We will dispatch the
             // fake control to the client, so that it can re-show the bar during this scenario.
-            return mDummyControlTarget;
+            return mTransientControlTarget;
         }
         final WindowState notificationShade = mPolicy.getNotificationShade();
         if (!canBeTopFullscreenOpaqueWindow(focusedWin)
@@ -603,7 +550,32 @@
         return focusedWin;
     }
 
-    boolean forcesShowingNavigationBars(WindowState win) {
+    boolean areTypesForciblyShowing(@InsetsType int types) {
+        return (mForcedShowingTypes & types) == types;
+    }
+
+    void updateSystemBars(WindowState win, boolean inSplitScreenMode, boolean inFreeformMode) {
+        mForcedShowingTypes = (inSplitScreenMode || inFreeformMode)
+                ? (Type.statusBars() | Type.navigationBars())
+                : forceShowingNavigationBars(win)
+                        ? Type.navigationBars()
+                        : 0;
+
+        // The client app won't be able to control these types of system bars. Here makes the client
+        // forcibly consume these types to prevent the app content from getting obscured.
+        mStateController.setForcedConsumingTypes(
+                mForcedShowingTypes | (remoteInsetsControllerControlsSystemBars(win)
+                        ? (Type.statusBars() | Type.navigationBars())
+                        : 0));
+
+        updateBarControlTarget(win);
+    }
+
+    private boolean forceShowingNavigationBars(WindowState win) {
+        // When "force show navigation bar" is enabled, it means both force visible is true, and
+        // we are in 3-button navigation. In this mode, the navigation bar is forcibly shown
+        // when activity type is ACTIVITY_TYPE_STANDARD which means Launcher or Recent could
+        // still control the navigation bar in this mode.
         return mPolicy.isForceShowNavigationBarEnabled() && win != null
                 && win.getActivityType() == ACTIVITY_TYPE_STANDARD;
     }
@@ -642,34 +614,6 @@
                 && (win.mAttrs.privateFlags & PRIVATE_FLAG_STATUS_FORCE_SHOW_NAVIGATION) != 0;
     }
 
-    @VisibleForTesting
-    void startAnimation(boolean show, Runnable callback) {
-        @InsetsType int typesReady = 0;
-        final SparseArray<InsetsSourceControl> controlsReady = new SparseArray<>();
-        final InsetsSourceControl[] controls =
-                mStateController.getControlsForDispatch(mDummyControlTarget);
-        if (controls == null) {
-            if (callback != null) {
-                DisplayThread.getHandler().post(callback);
-            }
-            return;
-        }
-        for (InsetsSourceControl control : controls) {
-            if (isTransient(control.getType()) && control.getLeash() != null) {
-                typesReady |= control.getType();
-                controlsReady.put(control.getId(), new InsetsSourceControl(control));
-            }
-        }
-        controlAnimationUnchecked(typesReady, controlsReady, show, callback);
-    }
-
-    private void controlAnimationUnchecked(int typesReady,
-            SparseArray<InsetsSourceControl> controls, boolean show, Runnable callback) {
-        InsetsPolicyAnimationControlListener listener =
-                new InsetsPolicyAnimationControlListener(show, callback, typesReady);
-        listener.mControlCallbacks.controlAnimationUnchecked(typesReady, controls, show);
-    }
-
     private void dispatchTransientSystemBarsVisibilityChanged(
             @Nullable WindowState focusedWindow,
             boolean areVisible,
@@ -696,6 +640,21 @@
                         wereRevealedFromSwipeOnSystemBar);
     }
 
+    void dump(String prefix, PrintWriter pw) {
+        pw.println(prefix + "InsetsPolicy");
+        prefix = prefix + "  ";
+        pw.println(prefix + "status: " + StatusBarManager.windowStateToString(mStatusBar.mState));
+        pw.println(prefix + "nav: " + StatusBarManager.windowStateToString(mNavBar.mState));
+        if (mShowingTransientTypes != 0) {
+            pw.println(prefix + "mShowingTransientTypes="
+                    + WindowInsets.Type.toString(mShowingTransientTypes));
+        }
+        if (mForcedShowingTypes != 0) {
+            pw.println(prefix + "mForcedShowingTypes="
+                    + WindowInsets.Type.toString(mForcedShowingTypes));
+        }
+    }
+
     private class BarWindow {
 
         private final int mId;
@@ -725,105 +684,138 @@
         }
     }
 
-    private class InsetsPolicyAnimationControlListener extends
-            InsetsController.InternalAnimationControlListener {
-        Runnable mFinishCallback;
-        InsetsPolicyAnimationControlCallbacks mControlCallbacks;
+    private static class ControlTarget implements InsetsControlTarget {
 
-        InsetsPolicyAnimationControlListener(boolean show, Runnable finishCallback, int types) {
-            super(show, false /* hasCallbacks */, types, BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE,
-                    false /* disable */, 0 /* floatingImeBottomInsets */,
-                    null /* loggingListener */, null /* jankContext */);
-            mFinishCallback = finishCallback;
-            mControlCallbacks = new InsetsPolicyAnimationControlCallbacks(this);
+        private final InsetsState mState = new InsetsState();
+        private final InsetsController mInsetsController;
+        private final InsetsStateController mStateController;
+        private final String mName;
+
+        ControlTarget(InsetsStateController stateController, Handler handler, String name) {
+            mStateController = stateController;
+            mInsetsController = new InsetsController(new Host(handler, name));
+            mName = name;
         }
 
         @Override
-        protected void onAnimationFinish() {
-            super.onAnimationFinish();
-            if (mFinishCallback != null) {
-                DisplayThread.getHandler().post(mFinishCallback);
-            }
+        public void notifyInsetsControlChanged() {
+            mState.set(mStateController.getRawInsetsState(), true /* copySources */);
+            mInsetsController.onStateChanged(mState);
+            mInsetsController.onControlsChanged(mStateController.getControlsForDispatch(this));
         }
 
-        private class InsetsPolicyAnimationControlCallbacks implements
-                InsetsAnimationControlCallbacks {
-            private InsetsAnimationControlImpl mAnimationControl = null;
-            private InsetsPolicyAnimationControlListener mListener;
+        @Override
+        public String toString() {
+            return mName;
+        }
+    }
 
-            InsetsPolicyAnimationControlCallbacks(InsetsPolicyAnimationControlListener listener) {
-                mListener = listener;
+    private static class Host implements InsetsController.Host {
+
+        private final float[] mTmpFloat9 = new float[9];
+        private final Handler mHandler;
+        private final String mName;
+
+        Host(Handler handler, String name) {
+            mHandler = handler;
+            mName = name;
+        }
+
+        @Override
+        public Handler getHandler() {
+            return mHandler;
+        }
+
+        @Override
+        public void notifyInsetsChanged() { }
+
+        @Override
+        public void dispatchWindowInsetsAnimationPrepare(
+                @NonNull WindowInsetsAnimation animation) { }
+
+        @Override
+        public Bounds dispatchWindowInsetsAnimationStart(
+                @NonNull WindowInsetsAnimation animation,
+                @NonNull Bounds bounds) {
+            return bounds;
+        }
+
+        @Override
+        public WindowInsets dispatchWindowInsetsAnimationProgress(
+                @NonNull WindowInsets insets,
+                @NonNull List<WindowInsetsAnimation> runningAnimations) {
+            return insets;
+        }
+
+        @Override
+        public void dispatchWindowInsetsAnimationEnd(
+                @NonNull WindowInsetsAnimation animation) { }
+
+        @Override
+        public void applySurfaceParams(SyncRtSurfaceTransactionApplier.SurfaceParams... p) {
+            final SurfaceControl.Transaction t = new SurfaceControl.Transaction();
+            for (int i = p.length - 1; i >= 0; i--) {
+                SyncRtSurfaceTransactionApplier.applyParams(t, p[i], mTmpFloat9);
             }
+            t.apply();
+            t.close();
+        }
 
-            private void controlAnimationUnchecked(int typesReady,
-                    SparseArray<InsetsSourceControl> controls, boolean show) {
-                if (typesReady == 0) {
-                    // nothing to animate.
-                    return;
-                }
-                mAnimatingShown = show;
+        @Override
+        public void updateRequestedVisibleTypes(int types) { }
 
-                final InsetsState state = mFocusedWin.getInsetsState();
+        @Override
+        public boolean hasAnimationCallbacks() {
+            return false;
+        }
 
-                // We are about to playing the default animation. Passing a null frame indicates
-                // the controlled types should be animated regardless of the frame.
-                mAnimationControl = new InsetsAnimationControlImpl(controls,
-                        null /* frame */, state, mListener, typesReady, this,
-                        mListener.getDurationMs(), getInsetsInterpolator(),
-                        show ? ANIMATION_TYPE_SHOW : ANIMATION_TYPE_HIDE, show
-                                ? LAYOUT_INSETS_DURING_ANIMATION_SHOWN
-                                : LAYOUT_INSETS_DURING_ANIMATION_HIDDEN,
-                        null /* translator */, null /* statsToken */);
-                SurfaceAnimationThread.getHandler().post(
-                        () -> mListener.onReady(mAnimationControl, typesReady));
-            }
+        @Override
+        public void setSystemBarsAppearance(int appearance, int mask) { }
 
-            /** Called on SurfaceAnimationThread without global WM lock held. */
-            @Override
-            public void scheduleApplyChangeInsets(InsetsAnimationControlRunner runner) {
-                if (mAnimationControl.applyChangeInsets(null /* outState */)) {
-                    mAnimationControl.finish(mAnimatingShown);
-                }
-            }
+        @Override
+        public int getSystemBarsAppearance() {
+            return 0;
+        }
 
-            @Override
-            public void notifyFinished(InsetsAnimationControlRunner runner, boolean shown) {
-                // Nothing's needed here. Finish steps is handled in the listener
-                // onAnimationFinished callback.
-            }
+        @Override
+        public void setSystemBarsBehavior(int behavior) { }
 
-            /** Called on SurfaceAnimationThread without global WM lock held. */
-            @Override
-            public void applySurfaceParams(
-                    final SyncRtSurfaceTransactionApplier.SurfaceParams... params) {
-                SurfaceControl.Transaction t = new SurfaceControl.Transaction();
-                for (int i = params.length - 1; i >= 0; i--) {
-                    SyncRtSurfaceTransactionApplier.SurfaceParams surfaceParams = params[i];
-                    applyParams(t, surfaceParams, mTmpFloat9);
-                }
-                t.apply();
-                t.close();
-            }
+        @Override
+        public int getSystemBarsBehavior() {
+            return BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE;
+        }
 
-            // Since we don't push applySurfaceParams to a Handler-queue we don't need
-            // to push release in this case.
-            @Override
-            public void releaseSurfaceControlFromRt(SurfaceControl sc) {
-                sc.release();
-            }
+        @Override
+        public void releaseSurfaceControlFromRt(SurfaceControl surfaceControl) {
+            surfaceControl.release();
+        }
 
-            @Override
-            public <T extends InsetsAnimationControlRunner & InternalInsetsAnimationController>
-            void startAnimation(T runner, WindowInsetsAnimationControlListener listener, int types,
-                    WindowInsetsAnimation animation,
-                    Bounds bounds) {
-            }
+        @Override
+        public void addOnPreDrawRunnable(Runnable r) { }
 
-            @Override
-            public void reportPerceptible(int types, boolean perceptible) {
-                // No-op for now - only client windows report perceptibility for now, with policy
-                // controllers assumed to always be perceptible.
-            }
+        @Override
+        public void postInsetsAnimationCallback(Runnable r) { }
+
+        @Override
+        public InputMethodManager getInputMethodManager() {
+            return null;
+        }
+
+        @Nullable
+        @Override
+        public String getRootViewTitle() {
+            return mName;
+        }
+
+        @Override
+        public int dipToPx(int dips) {
+            return 0;
+        }
+
+        @Nullable
+        @Override
+        public IBinder getWindowToken() {
+            return null;
         }
     }
 }
diff --git a/services/core/java/com/android/server/wm/InsetsSourceProvider.java b/services/core/java/com/android/server/wm/InsetsSourceProvider.java
index e1c865b..2b8312c 100644
--- a/services/core/java/com/android/server/wm/InsetsSourceProvider.java
+++ b/services/core/java/com/android/server/wm/InsetsSourceProvider.java
@@ -40,6 +40,7 @@
 import android.util.SparseArray;
 import android.util.proto.ProtoOutputStream;
 import android.view.InsetsSource;
+import android.view.InsetsSource.Flags;
 import android.view.InsetsSourceControl;
 import android.view.SurfaceControl;
 import android.view.SurfaceControl.Transaction;
@@ -81,6 +82,8 @@
     private final Rect mSourceFrame = new Rect();
     private final Rect mLastSourceFrame = new Rect();
     private @NonNull Insets mInsetsHint = Insets.NONE;
+    private @Flags int mFlagsFromFrameProvider;
+    private @Flags int mFlagsFromServer;
 
     private final Consumer<Transaction> mSetLeashPositionConsumer = t -> {
         if (mControl != null) {
@@ -189,6 +192,16 @@
         }
     }
 
+    boolean setFlags(@Flags int flags, @Flags int mask) {
+        mFlagsFromServer = (mFlagsFromServer & ~mask) | (flags & mask);
+        final @Flags int mergedFlags = mFlagsFromFrameProvider | mFlagsFromServer;
+        if (mSource.getFlags() != mergedFlags) {
+            mSource.setFlags(mergedFlags);
+            return true;
+        }
+        return false;
+    }
+
     /**
      * The source frame can affect the layout of other windows, so this should be called once the
      * window container gets laid out.
@@ -217,11 +230,11 @@
 
         mSourceFrame.set(frame);
         if (mFrameProvider != null) {
-            final int flags = mFrameProvider.apply(
+            mFlagsFromFrameProvider = mFrameProvider.apply(
                     mWindowContainer.getDisplayContent().mDisplayFrames,
                     mWindowContainer,
                     mSourceFrame);
-            mSource.setFlags(flags);
+            mSource.setFlags(mFlagsFromFrameProvider | mFlagsFromServer);
         }
         updateSourceFrameForServerVisibility();
 
diff --git a/services/core/java/com/android/server/wm/InsetsStateController.java b/services/core/java/com/android/server/wm/InsetsStateController.java
index addb521..081ebe0 100644
--- a/services/core/java/com/android/server/wm/InsetsStateController.java
+++ b/services/core/java/com/android/server/wm/InsetsStateController.java
@@ -17,6 +17,7 @@
 package com.android.server.wm;
 
 import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER;
+import static android.view.InsetsSource.FLAG_FORCE_CONSUMING;
 import static android.view.InsetsSource.ID_IME;
 import static android.view.WindowInsets.Type.displayCutout;
 import static android.view.WindowInsets.Type.ime;
@@ -85,6 +86,8 @@
         }
     };
 
+    private @InsetsType int mForcedConsumingTypes;
+
     InsetsStateController(DisplayContent displayContent) {
         mDisplayContent = displayContent;
     }
@@ -122,6 +125,11 @@
         provider = id == ID_IME
                 ? new ImeInsetsSourceProvider(source, this, mDisplayContent)
                 : new InsetsSourceProvider(source, this, mDisplayContent);
+        provider.setFlags(
+                (mForcedConsumingTypes & type) != 0
+                        ? FLAG_FORCE_CONSUMING
+                        : 0,
+                FLAG_FORCE_CONSUMING);
         mProviders.put(id, provider);
         return provider;
     }
@@ -137,6 +145,24 @@
         }
     }
 
+    void setForcedConsumingTypes(@InsetsType int types) {
+        if (mForcedConsumingTypes != types) {
+            mForcedConsumingTypes = types;
+            boolean changed = false;
+            for (int i = mProviders.size() - 1; i >= 0; i--) {
+                final InsetsSourceProvider provider = mProviders.valueAt(i);
+                changed |= provider.setFlags(
+                        (types & provider.getSource().getType()) != 0
+                                ? FLAG_FORCE_CONSUMING
+                                : 0,
+                        FLAG_FORCE_CONSUMING);
+            }
+            if (changed) {
+                notifyInsetsChanged();
+            }
+        }
+    }
+
     /**
      * Called when a layout pass has occurred.
      */
@@ -391,6 +417,10 @@
         for (int i = mProviders.size() - 1; i >= 0; i--) {
             mProviders.valueAt(i).dump(pw, prefix + "  ");
         }
+        if (mForcedConsumingTypes != 0) {
+            pw.println(prefix + "mForcedConsumingTypes="
+                    + WindowInsets.Type.toString(mForcedConsumingTypes));
+        }
     }
 
     void dumpDebug(ProtoOutputStream proto, @WindowTraceLogLevel int logLevel) {
diff --git a/services/core/java/com/android/server/wm/KeyguardController.java b/services/core/java/com/android/server/wm/KeyguardController.java
index ad9c3b2..83fd725 100644
--- a/services/core/java/com/android/server/wm/KeyguardController.java
+++ b/services/core/java/com/android/server/wm/KeyguardController.java
@@ -418,13 +418,17 @@
             return;
         }
 
-        final boolean waitAppTransition = isKeyguardLocked(displayId);
-        mWindowManager.mPolicy.onKeyguardOccludedChangedLw(isDisplayOccluded(DEFAULT_DISPLAY),
-                waitAppTransition);
-        if (waitAppTransition) {
-            mService.deferWindowLayout();
-            try {
-                if (isDisplayOccluded(DEFAULT_DISPLAY)) {
+        final TransitionController tc = mRootWindowContainer.mTransitionController;
+
+        final boolean occluded = isDisplayOccluded(displayId);
+        final boolean performTransition = isKeyguardLocked(displayId);
+        final boolean executeTransition = performTransition && !tc.isCollecting();
+
+        mWindowManager.mPolicy.onKeyguardOccludedChangedLw(occluded);
+        mService.deferWindowLayout();
+        try {
+            if (isKeyguardLocked(displayId)) {
+                if (occluded) {
                     mRootWindowContainer.getDefaultDisplay().requestTransitionAndLegacyPrepare(
                             TRANSIT_KEYGUARD_OCCLUDE,
                             TRANSIT_FLAG_KEYGUARD_OCCLUDING,
@@ -434,11 +438,19 @@
                             TRANSIT_KEYGUARD_UNOCCLUDE,
                             TRANSIT_FLAG_KEYGUARD_UNOCCLUDING);
                 }
-                updateKeyguardSleepToken(DEFAULT_DISPLAY);
-                mWindowManager.executeAppTransition();
-            } finally {
-                mService.continueWindowLayout();
+            } else {
+                if (tc.inTransition()) {
+                    tc.mStateValidators.add(mWindowManager.mPolicy::applyKeyguardOcclusionChange);
+                } else {
+                    mWindowManager.mPolicy.applyKeyguardOcclusionChange();
+                }
             }
+            updateKeyguardSleepToken(displayId);
+            if (performTransition && executeTransition) {
+                mWindowManager.executeAppTransition();
+            }
+        } finally {
+            mService.continueWindowLayout();
         }
     }
 
@@ -485,6 +497,9 @@
         }
     }
 
+    /**
+     * @return true if Keyguard is occluded or the device is dreaming.
+     */
     boolean isDisplayOccluded(int displayId) {
         return getDisplayState(displayId).mOccluded;
     }
diff --git a/services/core/java/com/android/server/wm/LaunchParamsPersister.java b/services/core/java/com/android/server/wm/LaunchParamsPersister.java
index bf511adf0..2394da9 100644
--- a/services/core/java/com/android/server/wm/LaunchParamsPersister.java
+++ b/services/core/java/com/android/server/wm/LaunchParamsPersister.java
@@ -433,7 +433,7 @@
             final byte[] data = saveParamsToXml();
 
             final File launchParamFolder = getLaunchParamFolder(mUserId);
-            if (!launchParamFolder.isDirectory() && !launchParamFolder.mkdirs()) {
+            if (!launchParamFolder.isDirectory() && !launchParamFolder.mkdir()) {
                 Slog.w(TAG, "Failed to create folder for " + mUserId);
                 return;
             }
diff --git a/services/core/java/com/android/server/wm/SafeActivityOptions.java b/services/core/java/com/android/server/wm/SafeActivityOptions.java
index 5860776..c914fa1 100644
--- a/services/core/java/com/android/server/wm/SafeActivityOptions.java
+++ b/services/core/java/com/android/server/wm/SafeActivityOptions.java
@@ -48,6 +48,7 @@
 import android.os.UserHandle;
 import android.util.Slog;
 import android.view.RemoteAnimationAdapter;
+import android.window.RemoteTransition;
 import android.window.WindowContainerToken;
 
 import com.android.internal.annotations.VisibleForTesting;
@@ -385,6 +386,18 @@
             throw new SecurityException(msg);
         }
 
+        // Check permission for remote transitions
+        final RemoteTransition transition = options.getRemoteTransition();
+        if (transition != null && supervisor.mService.checkPermission(
+                CONTROL_REMOTE_APP_TRANSITION_ANIMATIONS, callingPid, callingUid)
+                != PERMISSION_GRANTED) {
+            final String msg = "Permission Denial: starting " + getIntentString(intent)
+                    + " from " + callerApp + " (pid=" + callingPid
+                    + ", uid=" + callingUid + ") with remoteTransition";
+            Slog.w(TAG, msg);
+            throw new SecurityException(msg);
+        }
+
         // If launched from bubble is specified, then ensure that the caller is system or sysui.
         if (options.getLaunchedFromBubble() && !isSystemOrSystemUI(callingPid, callingUid)) {
             final String msg = "Permission Denial: starting " + getIntentString(intent)
diff --git a/services/core/java/com/android/server/wm/TaskPersister.java b/services/core/java/com/android/server/wm/TaskPersister.java
index 29c192c..f882b9b 100644
--- a/services/core/java/com/android/server/wm/TaskPersister.java
+++ b/services/core/java/com/android/server/wm/TaskPersister.java
@@ -509,7 +509,7 @@
 
     private static boolean createParentDirectory(String filePath) {
         File parentDir = new File(filePath).getParentFile();
-        return parentDir.exists() || parentDir.mkdirs();
+        return parentDir.isDirectory() || parentDir.mkdir();
     }
 
     private static class TaskWriteQueueItem implements PersisterQueue.WriteQueueItem {
diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java
index 79a54c3..5b3bbd5 100644
--- a/services/core/java/com/android/server/wm/Transition.java
+++ b/services/core/java/com/android/server/wm/Transition.java
@@ -27,6 +27,7 @@
 import static android.view.Display.DEFAULT_DISPLAY;
 import static android.view.Display.INVALID_DISPLAY;
 import static android.view.WindowManager.INPUT_CONSUMER_RECENTS_ANIMATION;
+import static android.view.WindowManager.KEYGUARD_VISIBILITY_TRANSIT_FLAGS;
 import static android.view.WindowManager.LayoutParams.ROTATION_ANIMATION_SEAMLESS;
 import static android.view.WindowManager.LayoutParams.ROTATION_ANIMATION_UNSPECIFIED;
 import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_STARTING;
@@ -77,6 +78,7 @@
 import android.os.Bundle;
 import android.os.IBinder;
 import android.os.IRemoteCallback;
+import android.os.Looper;
 import android.os.RemoteException;
 import android.os.SystemClock;
 import android.os.Trace;
@@ -613,12 +615,8 @@
             }
         }
         if (mParticipants.contains(wc)) return;
-        // Wallpaper is like in a static drawn state unless display may have changes, so exclude
-        // the case to reduce transition latency waiting for the unchanged wallpaper to redraw.
-        final boolean needSync = (!isWallpaper(wc) || mParticipants.contains(wc.mDisplayContent))
-                // Transient-hide may be hidden later, so no need to request redraw.
-                && !isInTransientHide(wc);
-        if (needSync) {
+        // Transient-hide may be hidden later, so no need to request redraw.
+        if (!isInTransientHide(wc)) {
             mSyncEngine.addToSyncSet(mSyncId, wc);
         }
         ChangeInfo info = mChanges.get(wc);
@@ -1101,6 +1099,16 @@
                 final Task task = ar.getTask();
                 if (task == null) continue;
                 boolean visibleAtTransitionEnd = mVisibleAtTransitionEndTokens.contains(ar);
+                // visibleAtTransitionEnd is used to guard against pre-maturely committing
+                // invisible on a window which is actually hidden by a later transition and not this
+                // one. However, for a transient launch, we can't use this mechanism because the
+                // visibility is determined at finish. Instead, use a different heuristic: don't
+                // commit invisible if the window is already in a later transition. That later
+                // transition will then handle the commit.
+                if (isTransientLaunch(ar) && !ar.isVisibleRequested()
+                        && mController.inCollectingTransition(ar)) {
+                    visibleAtTransitionEnd = true;
+                }
                 // We need both the expected visibility AND current requested-visibility to be
                 // false. If it is expected-visible but not currently visible, it means that
                 // another animation is queued-up to animate this to invisibility, so we can't
@@ -2657,7 +2665,7 @@
     }
 
     private void validateKeyguardOcclusion() {
-        if ((mFlags & TRANSIT_FLAG_KEYGUARD_LOCKED) != 0) {
+        if ((mFlags & KEYGUARD_VISIBILITY_TRANSIT_FLAGS) != 0) {
             mController.mStateValidators.add(
                 mController.mAtm.mWindowManager.mPolicy::applyKeyguardOcclusionChange);
         }
@@ -2686,6 +2694,26 @@
         });
     }
 
+    /**
+     * Returns {@code true} if the transition and the corresponding transaction should be applied
+     * on display thread. Currently, this only checks for display rotation change because the order
+     * of dispatching the new display info will be after requesting the windows to sync drawing.
+     * That avoids potential flickering of screen overlays (e.g. cutout, rounded corner). Also,
+     * because the display thread has a higher priority, it is faster to perform the configuration
+     * changes and window hierarchy traversal.
+     */
+    boolean shouldApplyOnDisplayThread() {
+        for (int i = mParticipants.size() - 1; i >= 0; --i) {
+            final DisplayContent dc = mParticipants.valueAt(i).asDisplayContent();
+            if (dc == null) continue;
+            final ChangeInfo changeInfo = mChanges.get(dc);
+            if (changeInfo != null && changeInfo.mRotation != dc.getRotation()) {
+                return Looper.myLooper() != mController.mAtm.mWindowManager.mH.getLooper();
+            }
+        }
+        return false;
+    }
+
     /** Applies the new configuration for the changed displays. */
     void applyDisplayChangeIfNeeded() {
         for (int i = mParticipants.size() - 1; i >= 0; --i) {
diff --git a/services/core/java/com/android/server/wm/TransitionController.java b/services/core/java/com/android/server/wm/TransitionController.java
index a539a48..79cb61b 100644
--- a/services/core/java/com/android/server/wm/TransitionController.java
+++ b/services/core/java/com/android/server/wm/TransitionController.java
@@ -588,15 +588,6 @@
     /** Sets the sync method for the display change. */
     private void setDisplaySyncMethod(@NonNull TransitionRequestInfo.DisplayChange displayChange,
             @NonNull Transition displayTransition, @NonNull DisplayContent displayContent) {
-        final int startRotation = displayChange.getStartRotation();
-        final int endRotation = displayChange.getEndRotation();
-        if (startRotation != endRotation && (startRotation + endRotation) % 2 == 0) {
-            // 180 degrees rotation change may not change screen size. So the clients may draw
-            // some frames before and after the display projection transaction is applied by the
-            // remote player. That may cause some buffers to show in different rotation. So use
-            // sync method to pause clients drawing until the projection transaction is applied.
-            mSyncEngine.setSyncMethod(displayTransition.getSyncId(), BLASTSyncEngine.METHOD_BLAST);
-        }
         final Rect startBounds = displayChange.getStartAbsBounds();
         final Rect endBounds = displayChange.getEndAbsBounds();
         if (startBounds == null || endBounds == null) return;
diff --git a/services/core/java/com/android/server/wm/WindowManagerInternal.java b/services/core/java/com/android/server/wm/WindowManagerInternal.java
index 9e7df00..805e7ff 100644
--- a/services/core/java/com/android/server/wm/WindowManagerInternal.java
+++ b/services/core/java/com/android/server/wm/WindowManagerInternal.java
@@ -30,6 +30,7 @@
 import android.hardware.display.DisplayManagerInternal;
 import android.os.Bundle;
 import android.os.IBinder;
+import android.os.Message;
 import android.util.Pair;
 import android.view.ContentRecordingSession;
 import android.view.Display;
@@ -515,12 +516,13 @@
      * Invalidate all visible windows on a given display, and report back on the callback when all
      * windows have redrawn.
      *
-     * @param callback reporting callback to be called when all windows have redrawn.
+     * @param message The message will be sent when all windows have redrawn. Note that the message
+     *                must be obtained from handler, otherwise it will throw NPE.
      * @param timeout calls the callback anyway after the timeout.
      * @param displayId waits for the windows on the given display, INVALID_DISPLAY to wait for all
      *                  windows on all displays.
      */
-    public abstract void waitForAllWindowsDrawn(Runnable callback, long timeout, int displayId);
+    public abstract void waitForAllWindowsDrawn(Message message, long timeout, int displayId);
 
     /**
      * Overrides the display size.
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index bb3d109..0e19671 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -601,7 +601,7 @@
      * The callbacks to make when the windows all have been drawn for a given
      * {@link WindowContainer}.
      */
-    final HashMap<WindowContainer, Runnable> mWaitingForDrawnCallbacks = new HashMap<>();
+    final ArrayMap<WindowContainer<?>, Message> mWaitingForDrawnCallbacks = new ArrayMap<>();
 
     /** List of window currently causing non-system overlay windows to be hidden. */
     private ArrayList<WindowState> mHidingNonSystemOverlayWindows = new ArrayList<>();
@@ -763,8 +763,6 @@
                 Settings.Secure.getUriFor(Settings.Secure.IMMERSIVE_MODE_CONFIRMATIONS);
         private final Uri mPolicyControlUri =
                 Settings.Global.getUriFor(Settings.Global.POLICY_CONTROL);
-        private final Uri mPointerLocationUri =
-                Settings.System.getUriFor(Settings.System.POINTER_LOCATION);
         private final Uri mForceDesktopModeOnExternalDisplaysUri = Settings.Global.getUriFor(
                         Settings.Global.DEVELOPMENT_FORCE_DESKTOP_MODE_ON_EXTERNAL_DISPLAYS);
         private final Uri mFreeformWindowUri = Settings.Global.getUriFor(
@@ -792,7 +790,6 @@
             resolver.registerContentObserver(mImmersiveModeConfirmationsUri, false, this,
                     UserHandle.USER_ALL);
             resolver.registerContentObserver(mPolicyControlUri, false, this, UserHandle.USER_ALL);
-            resolver.registerContentObserver(mPointerLocationUri, false, this, UserHandle.USER_ALL);
             resolver.registerContentObserver(mForceDesktopModeOnExternalDisplaysUri, false, this,
                     UserHandle.USER_ALL);
             resolver.registerContentObserver(mFreeformWindowUri, false, this, UserHandle.USER_ALL);
@@ -816,11 +813,6 @@
                 return;
             }
 
-            if (mPointerLocationUri.equals(uri)) {
-                updatePointerLocation();
-                return;
-            }
-
             if (mForceDesktopModeOnExternalDisplaysUri.equals(uri)) {
                 updateForceDesktopModeOnExternalDisplays();
                 return;
@@ -869,7 +861,6 @@
 
         void loadSettings() {
             updateSystemUiSettings(false /* handleChange */);
-            updatePointerLocation();
             updateMaximumObscuringOpacityForTouch();
         }
 
@@ -900,21 +891,6 @@
             }
         }
 
-        void updatePointerLocation() {
-            ContentResolver resolver = mContext.getContentResolver();
-            final boolean enablePointerLocation = Settings.System.getIntForUser(resolver,
-                    Settings.System.POINTER_LOCATION, 0, UserHandle.USER_CURRENT) != 0;
-
-            if (mPointerLocationEnabled == enablePointerLocation) {
-                return;
-            }
-            mPointerLocationEnabled = enablePointerLocation;
-            synchronized (mGlobalLock) {
-                mRoot.forAllDisplayPolicies(
-                        p -> p.setPointerLocationEnabled(mPointerLocationEnabled));
-            }
-        }
-
         void updateForceDesktopModeOnExternalDisplays() {
             ContentResolver resolver = mContext.getContentResolver();
             final boolean enableForceDesktopMode = Settings.Global.getInt(resolver,
@@ -5369,8 +5345,6 @@
         public static final int CLIENT_FREEZE_TIMEOUT = 30;
         public static final int NOTIFY_ACTIVITY_DRAWN = 32;
 
-        public static final int ALL_WINDOWS_DRAWN = 33;
-
         public static final int NEW_ANIMATOR_SCALE = 34;
 
         public static final int SHOW_EMULATOR_DISPLAY_OVERLAY = 36;
@@ -5492,7 +5466,7 @@
                 }
 
                 case WAITING_FOR_DRAWN_TIMEOUT: {
-                    Runnable callback = null;
+                    final Message callback;
                     final WindowContainer<?> container = (WindowContainer<?>) msg.obj;
                     synchronized (mGlobalLock) {
                         ProtoLog.w(WM_ERROR, "Timeout waiting for drawn: undrawn=%s",
@@ -5506,7 +5480,7 @@
                         callback = mWaitingForDrawnCallbacks.remove(container);
                     }
                     if (callback != null) {
-                        callback.run();
+                        callback.sendToTarget();
                     }
                     break;
                 }
@@ -5530,17 +5504,6 @@
                     }
                     break;
                 }
-                case ALL_WINDOWS_DRAWN: {
-                    Runnable callback;
-                    final WindowContainer container = (WindowContainer) msg.obj;
-                    synchronized (mGlobalLock) {
-                        callback = mWaitingForDrawnCallbacks.remove(container);
-                    }
-                    if (callback != null) {
-                        callback.run();
-                    }
-                    break;
-                }
                 case NEW_ANIMATOR_SCALE: {
                     float scale = getCurrentAnimatorScale();
                     ValueAnimator.setDurationScale(scale);
@@ -6098,7 +6061,8 @@
         if (mWaitingForDrawnCallbacks.isEmpty()) {
             return;
         }
-        mWaitingForDrawnCallbacks.forEach((container, callback) -> {
+        for (int i = mWaitingForDrawnCallbacks.size() - 1; i >= 0; i--) {
+            final WindowContainer<?> container = mWaitingForDrawnCallbacks.keyAt(i);
             for (int j = container.mWaitingForDrawn.size() - 1; j >= 0; j--) {
                 final WindowState win = (WindowState) container.mWaitingForDrawn.get(j);
                 ProtoLog.i(WM_DEBUG_SCREEN_ON,
@@ -6124,9 +6088,9 @@
             if (container.mWaitingForDrawn.isEmpty()) {
                 ProtoLog.d(WM_DEBUG_SCREEN_ON, "All windows drawn!");
                 mH.removeMessages(H.WAITING_FOR_DRAWN_TIMEOUT, container);
-                mH.sendMessage(mH.obtainMessage(H.ALL_WINDOWS_DRAWN, container));
+                mWaitingForDrawnCallbacks.removeAt(i).sendToTarget();
             }
-        });
+        }
     }
 
     private void traceStartWaitingForWindowDrawn(WindowState window) {
@@ -7842,13 +7806,14 @@
         }
 
         @Override
-        public void waitForAllWindowsDrawn(Runnable callback, long timeout, int displayId) {
+        public void waitForAllWindowsDrawn(Message message, long timeout, int displayId) {
+            Objects.requireNonNull(message.getTarget());
             final WindowContainer<?> container = displayId == INVALID_DISPLAY
                     ? mRoot : mRoot.getDisplayContent(displayId);
             if (container == null) {
                 // The waiting container doesn't exist, no need to wait to run the callback. Run and
                 // return;
-                callback.run();
+                message.sendToTarget();
                 return;
             }
             boolean allWindowsDrawn = false;
@@ -7865,13 +7830,13 @@
                         }
                     }
 
-                    mWaitingForDrawnCallbacks.put(container, callback);
+                    mWaitingForDrawnCallbacks.put(container, message);
                     mH.sendNewMessageDelayed(H.WAITING_FOR_DRAWN_TIMEOUT, container, timeout);
                     checkDrawnWindowsLocked();
                 }
             }
             if (allWindowsDrawn) {
-                callback.run();
+                message.sendToTarget();
             }
         }
 
diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java
index a2f7ba4..31918f4 100644
--- a/services/core/java/com/android/server/wm/WindowOrganizerController.java
+++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java
@@ -321,9 +321,18 @@
                     applyTransaction(wct, -1 /*syncId*/, null /*transition*/, caller);
                     return transition.getToken();
                 }
-                transition.start();
                 transition.mLogger.mStartWCT = wct;
-                applyTransaction(wct, -1 /*syncId*/, transition, caller);
+                if (transition.shouldApplyOnDisplayThread()) {
+                    mService.mH.post(() -> {
+                        synchronized (mService.mGlobalLock) {
+                            transition.start();
+                            applyTransaction(wct, -1 /* syncId */, transition, caller);
+                        }
+                    });
+                } else {
+                    transition.start();
+                    applyTransaction(wct, -1 /* syncId */, transition, caller);
+                }
                 // Since the transition is already provided, it means WMCore is determining the
                 // "readiness lifecycle" outside the provided transaction, so don't set ready here.
                 return transition.getToken();
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index cf1e51f..0d4c2d6 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -5665,7 +5665,7 @@
         }
         if (mIsWallpaper) {
             // TODO(b/233286785): Add sync support to wallpaper.
-            return false;
+            return true;
         }
         // In the WindowContainer implementation we immediately mark ready
         // since a generic WindowContainer only needs to wait for its
diff --git a/services/core/jni/com_android_server_input_InputManagerService.cpp b/services/core/jni/com_android_server_input_InputManagerService.cpp
index e76cbe44..c065cb5 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(false);
+            sysprop::InputProperties::enable_input_device_usage_metrics().value_or(true);
     if (!ENABLE_INPUT_DEVICE_USAGE_METRICS) return;
 
     mInputManager->getMetricsCollector().notifyDeviceInteraction(deviceId, timestamp, uids);
diff --git a/services/credentials/java/com/android/server/credentials/ProviderRegistryGetSession.java b/services/credentials/java/com/android/server/credentials/ProviderRegistryGetSession.java
index 46c90b4..bafa4a5 100644
--- a/services/credentials/java/com/android/server/credentials/ProviderRegistryGetSession.java
+++ b/services/credentials/java/com/android/server/credentials/ProviderRegistryGetSession.java
@@ -126,6 +126,7 @@
         mElementKeys = new HashSet<>(requestOption
                 .getCredentialRetrievalData()
                 .getStringArrayList(CredentialOption.SUPPORTED_ELEMENT_KEYS));
+        mStatus = Status.PENDING;
     }
 
     protected ProviderRegistryGetSession(@NonNull Context context,
@@ -143,6 +144,7 @@
         mElementKeys = new HashSet<>(requestOption
                 .getCredentialRetrievalData()
                 .getStringArrayList(CredentialOption.SUPPORTED_ELEMENT_KEYS));
+        mStatus = Status.PENDING;
     }
 
     private List<Entry> prepareUiCredentialEntries(
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 417fc39..fe913b9 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -16229,6 +16229,11 @@
         }
 
         @Override
+        public ComponentName getDeviceOwnerComponent(boolean callingUserOnly) {
+            return DevicePolicyManagerService.this.getDeviceOwnerComponent(callingUserOnly);
+        }
+
+        @Override
         public int getDeviceOwnerUserId() {
             return DevicePolicyManagerService.this.getDeviceOwnerUserId();
         }
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/PackageManagerServiceTests/appenumeration/src/com/android/server/pm/test/appenumeration/CrossUserPackageVisibilityTests.java b/services/tests/PackageManagerServiceTests/appenumeration/src/com/android/server/pm/test/appenumeration/CrossUserPackageVisibilityTests.java
index e33ca77..b7a0cf3 100644
--- a/services/tests/PackageManagerServiceTests/appenumeration/src/com/android/server/pm/test/appenumeration/CrossUserPackageVisibilityTests.java
+++ b/services/tests/PackageManagerServiceTests/appenumeration/src/com/android/server/pm/test/appenumeration/CrossUserPackageVisibilityTests.java
@@ -138,6 +138,14 @@
     }
 
     @Test
+    public void testGetUserMinAspectRatio_withCrossUserId() {
+        final int crossUserId = UserHandle.myUserId() + 1;
+        assertThrows(SecurityException.class,
+                () -> mIPackageManager.getUserMinAspectRatio(
+                        mInstrumentation.getContext().getPackageName(), crossUserId));
+    }
+
+    @Test
     public void testIsPackageSignedByKeySet_cannotDetectCrossUserPkg() throws Exception {
         final KeySet keySet = mIPackageManager.getSigningKeySet(mContext.getPackageName());
         assertThrows(IllegalArgumentException.class,
diff --git a/services/tests/PackageManagerServiceTests/server/res/raw/install_app2_cert5_rotated_cert6 b/services/tests/PackageManagerServiceTests/server/res/raw/install_app2_cert5_rotated_cert6
index 30bb647..6feebb8 100644
--- a/services/tests/PackageManagerServiceTests/server/res/raw/install_app2_cert5_rotated_cert6
+++ b/services/tests/PackageManagerServiceTests/server/res/raw/install_app2_cert5_rotated_cert6
Binary files differ
diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerServiceTest.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerServiceTest.java
index 435f0d7..a805e5c 100644
--- a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerServiceTest.java
+++ b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerServiceTest.java
@@ -20,6 +20,7 @@
 
 import static com.google.common.truth.Truth.assertWithMessage;
 
+import static org.junit.Assert.assertThrows;
 import static org.junit.Assert.fail;
 
 import static java.lang.reflect.Modifier.isFinal;
@@ -138,7 +139,7 @@
                 .setSecondaryCpuAbiString("secondaryCpuAbiString")
                 .setCpuAbiOverrideString("cpuAbiOverrideString")
                 .build();
-        pri.populateUsers(new int[] {
+        pri.populateUsers(new int[]{
                 1, 2, 3, 4, 5
         }, setting);
         Assert.assertNotNull(pri.mBroadcastUsers);
@@ -150,7 +151,7 @@
         pri.mBroadcastUsers = null;
         final int EXCLUDED_USER_ID = 4;
         setting.setInstantApp(true, EXCLUDED_USER_ID);
-        pri.populateUsers(new int[] {
+        pri.populateUsers(new int[]{
                 1, 2, 3, EXCLUDED_USER_ID, 5
         }, setting);
         Assert.assertNotNull(pri.mBroadcastUsers);
@@ -164,8 +165,8 @@
 
     @Test
     public void testPartitions() {
-        String[] partitions = { "system", "vendor", "odm", "oem", "product", "system_ext" };
-        String[] appdir = { "app", "priv-app" };
+        String[] partitions = {"system", "vendor", "odm", "oem", "product", "system_ext"};
+        String[] appdir = {"app", "priv-app"};
         for (int i = 0; i < partitions.length; i++) {
             final ScanPartition scanPartition =
                     PackageManagerService.SYSTEM_PARTITIONS.get(i);
@@ -425,10 +426,10 @@
     private String displayName(Method m) {
         String r = m.getName();
         String p = Arrays.toString(m.getGenericParameterTypes())
-                   .replaceAll("([a-zA-Z0-9]+\\.)+", "")
-                   .replace("class ", "")
-                   .replaceAll("^\\[", "(")
-                   .replaceAll("\\]$", ")");
+                .replaceAll("([a-zA-Z0-9]+\\.)+", "")
+                .replace("class ", "")
+                .replaceAll("^\\[", "(")
+                .replaceAll("\\]$", ")");
         return r + p;
     }
 
@@ -612,4 +613,22 @@
             runShellCommand("pm uninstall " + TEST_PKG_NAME);
         }
     }
+
+    @Test
+    public void testSetUserMinAspectRatio_samePackage_succeeds() throws Exception {
+        mIPackageManager.setUserMinAspectRatio(PACKAGE_NAME, UserHandle.myUserId(),
+                PackageManager.USER_MIN_ASPECT_RATIO_UNSET);
+        // Invoking setUserMinAspectRatio on the same package shouldn't get any exception.
+    }
+
+    @Test
+    public void testSetUserMinAspectRatio_differentPackage_fails() {
+        final File testApk = new File(TEST_DATA_PATH, TEST_APP_APK);
+        runShellCommand("pm install " + testApk);
+        assertThrows(SecurityException.class, () -> {
+            mIPackageManager.setUserMinAspectRatio(TEST_PKG_NAME, UserHandle.myUserId(),
+                    PackageManager.USER_MIN_ASPECT_RATIO_UNSET);
+        });
+        runShellCommand("pm uninstall " + TEST_PKG_NAME);
+    }
 }
diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerSettingsTests.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerSettingsTests.java
index 836f858..16fb012 100644
--- a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerSettingsTests.java
+++ b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerSettingsTests.java
@@ -971,7 +971,7 @@
         origPkgSetting01.setUserState(0, 100, 1, true, false, false, false, 0, null, false,
                 false, "lastDisabledCaller", new ArraySet<>(new String[]{"enabledComponent1"}),
                 new ArraySet<>(new String[]{"disabledComponent1"}), 0, 0, "harmfulAppWarning",
-                "splashScreenTheme", 1000L);
+                "splashScreenTheme", 1000L, PackageManager.USER_MIN_ASPECT_RATIO_UNSET);
         final PersistableBundle appExtras1 = createPersistableBundle(
                 PACKAGE_NAME_1, 1L, 0.01, true, "appString1");
         final PersistableBundle launcherExtras1 = createPersistableBundle(
@@ -1638,7 +1638,8 @@
                 : oldUserState.getSharedLibraryOverlayPaths() == null)
                 && userState.getSplashScreenTheme().equals(
                         oldUserState.getSplashScreenTheme())
-                && userState.getUninstallReason() == oldUserState.getUninstallReason();
+                && userState.getUninstallReason() == oldUserState.getUninstallReason()
+                && userState.getMinAspectRatio() == oldUserState.getMinAspectRatio();
     }
 
     private SharedUserSetting createSharedUserSetting(Settings settings, String userName,
diff --git a/services/tests/mockingservicestests/src/com/android/server/display/LocalDisplayAdapterTest.java b/services/tests/mockingservicestests/src/com/android/server/display/LocalDisplayAdapterTest.java
index f89f73c9..aa0a2fe 100644
--- a/services/tests/mockingservicestests/src/com/android/server/display/LocalDisplayAdapterTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/display/LocalDisplayAdapterTest.java
@@ -1257,6 +1257,17 @@
         public LocalDisplayAdapter.SurfaceControlProxy getSurfaceControlProxy() {
             return mSurfaceControlProxy;
         }
+
+        // Instead of using DisplayDeviceConfig.create(context, physicalDisplayId, isFirstDisplay)
+        // we should use DisplayDeviceConfig.create(context, isFirstDisplay) for the test to ensure
+        // that real device DisplayDeviceConfig is not loaded for FakeDisplay and we are getting
+        // consistent behaviour. Please also note that context passed to this method, is
+        // mMockContext and values will be loaded from mMockResources.
+        @Override
+        public DisplayDeviceConfig createDisplayDeviceConfig(Context context,
+                long physicalDisplayId, boolean isFirstDisplay) {
+            return DisplayDeviceConfig.create(context, isFirstDisplay);
+        }
     }
 
     private class TestListener implements DisplayAdapter.Listener {
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerServiceTest.java
index e7b3e6f..617e4eb 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerServiceTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerServiceTest.java
@@ -325,6 +325,83 @@
                 () -> mUmi.getBootUser(/* waitUntilSet= */ false));
     }
 
+    @Test
+    public void testGetPreviousFullUserToEnterForeground() throws Exception {
+        addUser(USER_ID);
+        setLastForegroundTime(USER_ID, 1_000_000L);
+        addUser(OTHER_USER_ID);
+        setLastForegroundTime(OTHER_USER_ID, 2_000_000L);
+
+        assertWithMessage("getPreviousFullUserToEnterForeground")
+                .that(mUms.getPreviousFullUserToEnterForeground())
+                .isEqualTo(OTHER_USER_ID);
+    }
+
+    @Test
+    public void testGetPreviousFullUserToEnterForeground_SkipsCurrentUser() throws Exception {
+        addUser(USER_ID);
+        setLastForegroundTime(USER_ID, 1_000_000L);
+        addUser(OTHER_USER_ID);
+        setLastForegroundTime(OTHER_USER_ID, 2_000_000L);
+
+        mockCurrentUser(OTHER_USER_ID);
+        assertWithMessage("getPreviousFullUserToEnterForeground should skip current user")
+                .that(mUms.getPreviousFullUserToEnterForeground())
+                .isEqualTo(USER_ID);
+    }
+
+    @Test
+    public void testGetPreviousFullUserToEnterForeground_SkipsNonFullUsers() throws Exception {
+        addUser(USER_ID);
+        setLastForegroundTime(USER_ID, 1_000_000L);
+        addUser(OTHER_USER_ID);
+        setLastForegroundTime(OTHER_USER_ID, 2_000_000L);
+
+        mUsers.get(OTHER_USER_ID).info.flags &= ~UserInfo.FLAG_FULL;
+        assertWithMessage("getPreviousFullUserToEnterForeground should skip non-full users")
+                .that(mUms.getPreviousFullUserToEnterForeground())
+                .isEqualTo(USER_ID);
+    }
+
+    @Test
+    public void testGetPreviousFullUserToEnterForeground_SkipsPartialUsers() throws Exception {
+        addUser(USER_ID);
+        setLastForegroundTime(USER_ID, 1_000_000L);
+        addUser(OTHER_USER_ID);
+        setLastForegroundTime(OTHER_USER_ID, 2_000_000L);
+
+        mUsers.get(OTHER_USER_ID).info.partial = true;
+        assertWithMessage("getPreviousFullUserToEnterForeground should skip partial users")
+                .that(mUms.getPreviousFullUserToEnterForeground())
+                .isEqualTo(USER_ID);
+    }
+
+    @Test
+    public void testGetPreviousFullUserToEnterForeground_SkipsDisabledUsers() throws Exception {
+        addUser(USER_ID);
+        setLastForegroundTime(USER_ID, 1_000_000L);
+        addUser(OTHER_USER_ID);
+        setLastForegroundTime(OTHER_USER_ID, 2_000_000L);
+
+        mUsers.get(OTHER_USER_ID).info.flags |= UserInfo.FLAG_DISABLED;
+        assertWithMessage("getPreviousFullUserToEnterForeground should skip disabled users")
+                .that(mUms.getPreviousFullUserToEnterForeground())
+                .isEqualTo(USER_ID);
+    }
+
+    @Test
+    public void testGetPreviousFullUserToEnterForeground_SkipsRemovingUsers() throws Exception {
+        addUser(USER_ID);
+        setLastForegroundTime(USER_ID, 1_000_000L);
+        addUser(OTHER_USER_ID);
+        setLastForegroundTime(OTHER_USER_ID, 2_000_000L);
+
+        mUms.addRemovingUserId(OTHER_USER_ID);
+        assertWithMessage("getPreviousFullUserToEnterForeground should skip removing users")
+                .that(mUms.getPreviousFullUserToEnterForeground())
+                .isEqualTo(USER_ID);
+    }
+
     private void mockCurrentUser(@UserIdInt int userId) {
         mockGetLocalService(ActivityManagerInternal.class, mActivityManagerInternal);
 
diff --git a/services/tests/mockingservicestests/src/com/android/server/utils/AlarmQueueTest.java b/services/tests/mockingservicestests/src/com/android/server/utils/AlarmQueueTest.java
index a3a49d70..f3aa427 100644
--- a/services/tests/mockingservicestests/src/com/android/server/utils/AlarmQueueTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/utils/AlarmQueueTest.java
@@ -259,6 +259,29 @@
     }
 
     @Test
+    public void testMinTimeBetweenAlarms_freshAlarm() {
+        final AlarmQueue<String> alarmQueue = createAlarmQueue(true, 5 * MINUTE_IN_MILLIS);
+        final long fixedTimeElapsed = mInjector.getElapsedRealtime();
+
+        InOrder inOrder = inOrder(mAlarmManager);
+
+        final String pkg1 = "com.android.test.1";
+        final String pkg2 = "com.android.test.2";
+        alarmQueue.addAlarm(pkg1, fixedTimeElapsed + MINUTE_IN_MILLIS);
+        inOrder.verify(mAlarmManager, timeout(1000).times(1)).setExact(
+                anyInt(), eq(fixedTimeElapsed + MINUTE_IN_MILLIS), eq(ALARM_TAG), any(), any());
+
+        advanceElapsedClock(MINUTE_IN_MILLIS);
+
+        alarmQueue.onAlarm();
+        // Minimum of 5 minutes between alarms, so the next alarm should be 5 minutes after the
+        // first.
+        alarmQueue.addAlarm(pkg2, fixedTimeElapsed + 2 * MINUTE_IN_MILLIS);
+        inOrder.verify(mAlarmManager, timeout(1000).times(1)).setExact(
+                anyInt(), eq(fixedTimeElapsed + 6 * MINUTE_IN_MILLIS), eq(ALARM_TAG), any(), any());
+    }
+
+    @Test
     public void testOnAlarm() {
         final AlarmQueue<String> alarmQueue = createAlarmQueue(true, 0);
         final long nowElapsed = mInjector.getElapsedRealtime();
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationControllerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationControllerTest.java
index e6ef044..5b5c8d4 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationControllerTest.java
@@ -29,6 +29,7 @@
 import static org.mockito.ArgumentMatchers.anyFloat;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.atLeastOnce;
 import static org.mockito.Mockito.doAnswer;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.mock;
@@ -202,6 +203,7 @@
         assertFalse(mFullScreenMagnificationController.isRegistered(DISPLAY_0));
         assertFalse(mFullScreenMagnificationController.isRegistered(DISPLAY_1));
 
+        // Once for each display on unregister
         verify(mMockThumbnail, times(2)).hideThumbnail();
     }
 
@@ -543,7 +545,11 @@
         // The first time is triggered when the thumbnail is just created.
         // The second time is triggered when the magnification region changed.
         verify(mMockThumbnail, times(2)).setThumbnailBounds(
-                any(), anyFloat(), anyFloat(), anyFloat());
+                /* currentBounds= */ any(),
+                /* scale= */ anyFloat(),
+                /* centerX= */ anyFloat(),
+                /* centerY= */ anyFloat()
+        );
     }
 
     @Test
@@ -681,6 +687,9 @@
         checkActivatedAndMagnifying(/* activated= */ true, /* magnifying= */ true, displayId);
         assertTrue(mFullScreenMagnificationController.resetIfNeeded(displayId, SERVICE_ID_2));
         checkActivatedAndMagnifying(/* activated= */ false, /* magnifying= */ false, displayId);
+
+        // Once on init before it's activated and once for reset
+        verify(mMockThumbnail, times(2)).hideThumbnail();
     }
 
     @Test
@@ -783,6 +792,9 @@
         mMessageCapturingHandler.sendAllMessages();
         checkActivatedAndMagnifying(/* activated= */ false, /* magnifying= */ false, DISPLAY_0);
         checkActivatedAndMagnifying(/* activated= */ false, /* magnifying= */ false, DISPLAY_1);
+
+        // Twice for each display: once on init before it's activated and once for screen off
+        verify(mMockThumbnail, times(4)).hideThumbnail();
     }
 
     @Test
@@ -847,6 +859,15 @@
         mMessageCapturingHandler.sendAllMessages();
         checkActivatedAndMagnifying(
                 /* activated= */ expectedActivated, /* magnifying= */ false, displayId);
+
+        if (expectedActivated) {
+            verify(mMockThumbnail, times(2)).setThumbnailBounds(
+                    /* currentBounds= */ any(),
+                    /* scale= */ anyFloat(),
+                    /* centerX= */ anyFloat(),
+                    /* centerY= */ anyFloat()
+            );
+        }
     }
 
     @Test
@@ -950,6 +971,13 @@
                 INITIAL_BOUNDS_LOWER_RIGHT_2X_CENTER, scale);
         assertThat(endSpec, closeTo(getMagnificationSpec(scale, expectedOffsets)));
         verify(mMockWindowManager).setMagnificationSpec(eq(displayId), argThat(closeTo(endSpec)));
+
+        verify(mMockThumbnail, atLeastOnce()).setThumbnailBounds(
+                /* currentBounds= */ any(),
+                eq(scale),
+                /* centerX= */ anyFloat(),
+                /* centerY= */ anyFloat()
+        );
     }
 
     @Test
@@ -984,6 +1012,13 @@
                 INITIAL_BOUNDS_LOWER_RIGHT_2X_CENTER, scale);
         assertThat(endSpec, closeTo(getMagnificationSpec(scale, expectedOffsets)));
         verify(mMockWindowManager).setMagnificationSpec(eq(displayId), argThat(closeTo(endSpec)));
+
+        verify(mMockThumbnail, atLeastOnce()).setThumbnailBounds(
+                /* currentBounds= */ any(),
+                eq(scale),
+                /* centerX= */ anyFloat(),
+                /* centerY= */ anyFloat()
+        );
     }
 
     @Test
@@ -1246,6 +1281,13 @@
         callbacks.onImeWindowVisibilityChanged(true);
         mMessageCapturingHandler.sendAllMessages();
         verify(mRequestObserver).onImeWindowVisibilityChanged(eq(DISPLAY_0), eq(true));
+
+        verify(mMockThumbnail, atLeastOnce()).setThumbnailBounds(
+                /* currentBounds= */ any(),
+                /* scale= */ anyFloat(),
+                /* centerX= */ anyFloat(),
+                /* centerY= */ anyFloat()
+        );
     }
 
     @Test
@@ -1270,6 +1312,15 @@
         mFullScreenMagnificationController.onUserContextChanged(DISPLAY_0);
 
         verify(mRequestObserver).onFullScreenMagnificationActivationState(eq(DISPLAY_0), eq(false));
+        verify(mMockThumbnail).setThumbnailBounds(
+                /* currentBounds= */ any(),
+                /* scale= */ anyFloat(),
+                /* centerX= */ anyFloat(),
+                /* centerY= */ anyFloat()
+        );
+
+        // Once on init before it's activated and once for reset
+        verify(mMockThumbnail, times(2)).hideThumbnail();
     }
 
     @Test
@@ -1281,6 +1332,12 @@
 
         assertEquals(1.0f, mFullScreenMagnificationController.getScale(DISPLAY_0), 0);
         assertTrue(mFullScreenMagnificationController.isActivated(DISPLAY_0));
+        verify(mMockThumbnail).setThumbnailBounds(
+                /* currentBounds= */ any(),
+                /* scale= */ anyFloat(),
+                /* centerX= */ anyFloat(),
+                /* centerY= */ anyFloat()
+        );
     }
 
     private void setScaleToMagnifying() {
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationControllerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationControllerTest.java
index e75fc21..81dd961 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationControllerTest.java
@@ -666,6 +666,21 @@
         assertEquals(config.getCenterX(), actualConfig.getCenterX(), 0);
         assertEquals(config.getCenterY(), actualConfig.getCenterY(), 0);
         assertEquals(config.getScale(), actualConfig.getScale(), 0);
+
+        verify(mWindowMagnificationManager).onUserMagnificationScaleChanged(
+                /* userId= */ anyInt(), eq(TEST_DISPLAY), eq(config.getScale()));
+    }
+
+    @Test
+    public void onSourceBoundChanged_windowEnabled_notifyMagnificationChanged()
+            throws RemoteException {
+        setMagnificationEnabled(MODE_WINDOW);
+        reset(mWindowMagnificationManager);
+
+        mMagnificationController.onSourceBoundsChanged(TEST_DISPLAY, TEST_RECT);
+
+        verify(mWindowMagnificationManager).onUserMagnificationScaleChanged(
+                /* userId= */ anyInt(), eq(TEST_DISPLAY), eq(DEFAULT_SCALE));
     }
 
     @Test
@@ -794,6 +809,7 @@
         verify(mMagnificationController).onWindowMagnificationActivationState(
                 eq(TEST_DISPLAY), eq(true));
     }
+
     @Test
     public void deactivateWindowMagnification_windowActivated_triggerCallbackAndLogUsage()
             throws RemoteException {
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationThumbnailTest.java b/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationThumbnailTest.java
index 3baa102..8faddf8 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationThumbnailTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationThumbnailTest.java
@@ -187,6 +187,29 @@
                 .addView(eq(mMagnificationThumbnail.mThumbnailLayout), any());
         verify(mMockWindowManager, never())
                 .removeView(eq(mMagnificationThumbnail.mThumbnailLayout));
+        verify(mMockWindowManager, never())
+                .updateViewLayout(eq(mMagnificationThumbnail.mThumbnailLayout), any());
+    }
+
+    @Test
+    public void whenVisible_setBoundsUpdatesLayout() throws InterruptedException {
+        runOnMainSync(() -> mMagnificationThumbnail.updateThumbnail(
+                /* scale=   */ 2f,
+                /* centerX= */ 5,
+                /* centerY= */ 10
+        ));
+        runOnMainSync(() -> mMagnificationThumbnail.setThumbnailBounds(
+                new Rect(),
+                /* scale=   */ 2f,
+                /* centerX= */ 5,
+                /* centerY= */ 10
+        ));
+        idle();
+
+        verify(mMockWindowManager).updateViewLayout(
+                eq(mMagnificationThumbnail.mThumbnailLayout),
+                /* params= */ any()
+        );
     }
 
     private static void idle() {
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/magnification/WindowMagnificationConnectionWrapperTest.java b/services/tests/servicestests/src/com/android/server/accessibility/magnification/WindowMagnificationConnectionWrapperTest.java
index 2357e65..8608199 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/magnification/WindowMagnificationConnectionWrapperTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/magnification/WindowMagnificationConnectionWrapperTest.java
@@ -122,6 +122,15 @@
     }
 
     @Test
+    public void onUserMagnificationScaleChanged() throws RemoteException {
+        final int testUserId = 1;
+        final float testScale = 3f;
+        mConnectionWrapper.onUserMagnificationScaleChanged(testUserId, TEST_DISPLAY, testScale);
+        verify(mConnection).onUserMagnificationScaleChanged(
+                eq(testUserId), eq(TEST_DISPLAY), eq(testScale));
+    }
+
+    @Test
     public void setMirrorWindowCallback() throws RemoteException {
         mConnectionWrapper.setConnectionCallback(mCallback);
         verify(mConnection).setConnectionCallback(mCallback);
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/magnification/WindowMagnificationManagerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/magnification/WindowMagnificationManagerTest.java
index c98de7c..e8b337a 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/magnification/WindowMagnificationManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/magnification/WindowMagnificationManagerTest.java
@@ -548,6 +548,18 @@
     }
 
     @Test
+    public void onUserMagnificationScaleChanged_hasConnection_invokeConnectionMethod()
+            throws RemoteException {
+        mWindowMagnificationManager.setConnection(mMockConnection.getConnection());
+
+        final float testScale = 3f;
+        mWindowMagnificationManager.onUserMagnificationScaleChanged(
+                CURRENT_USER_ID, TEST_DISPLAY, testScale);
+        verify(mMockConnection.getConnection()).onUserMagnificationScaleChanged(
+                eq(CURRENT_USER_ID), eq(TEST_DISPLAY), eq(testScale));
+    }
+
+    @Test
     public void pointersInWindow_magnifierEnabled_returnCorrectValue() throws RemoteException {
         mWindowMagnificationManager.setConnection(mMockConnection.getConnection());
         mWindowMagnificationManager.enableWindowMagnification(TEST_DISPLAY, 3.0f, NaN, NaN);
diff --git a/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java b/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java
index dccacb4..24a628e 100644
--- a/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java
@@ -1342,6 +1342,10 @@
             Message copy = new Message();
             copy.copyFrom(msg);
             mMessages.add(copy);
+            if (msg.getCallback() != null) {
+                msg.getCallback().run();
+                msg.setCallback(null);
+            }
             return super.sendMessageAtTime(msg, uptimeMillis);
         }
     }
diff --git a/services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionManagerServiceTest.java
index c42928e..bb8b986 100644
--- a/services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionManagerServiceTest.java
@@ -38,6 +38,7 @@
 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 static org.testng.Assert.assertThrows;
 
 import android.app.ActivityManagerInternal;
@@ -49,10 +50,14 @@
 import android.content.pm.PackageManager.NameNotFoundException;
 import android.media.projection.IMediaProjection;
 import android.media.projection.IMediaProjectionCallback;
+import android.media.projection.IMediaProjectionWatcherCallback;
 import android.media.projection.ReviewGrantedConsentResult;
+import android.os.Binder;
 import android.os.IBinder;
+import android.os.Looper;
 import android.os.RemoteException;
 import android.os.UserHandle;
+import android.os.test.TestLooper;
 import android.platform.test.annotations.Presubmit;
 import android.view.ContentRecordingSession;
 
@@ -86,6 +91,7 @@
     private static final int UID = 10;
     private static final String PACKAGE_NAME = "test.package";
     private final ApplicationInfo mAppInfo = new ApplicationInfo();
+    private final TestLooper mTestLooper = new TestLooper();
     private static final ContentRecordingSession DISPLAY_SESSION =
             ContentRecordingSession.createDisplaySession(DEFAULT_DISPLAY);
     // Callback registered by an app on a MediaProjection instance.
@@ -110,6 +116,14 @@
                 }
             };
 
+    private final MediaProjectionManagerService.Injector mTestLooperInjector =
+            new MediaProjectionManagerService.Injector() {
+                @Override
+                Looper createCallbackLooper() {
+                    return mTestLooper.getLooper();
+                }
+            };
+
     private Context mContext;
     private MediaProjectionManagerService mService;
     private OffsettableClock mClock;
@@ -122,12 +136,15 @@
     private WindowManagerInternal mWindowManagerInternal;
     @Mock
     private PackageManager mPackageManager;
+    @Mock
+    private IMediaProjectionWatcherCallback mWatcherCallback;
     @Captor
     private ArgumentCaptor<ContentRecordingSession> mSessionCaptor;
 
     @Before
     public void setup() throws Exception {
         MockitoAnnotations.initMocks(this);
+        when(mWatcherCallback.asBinder()).thenReturn(new Binder());
 
         LocalServices.removeServiceForTest(ActivityManagerInternal.class);
         LocalServices.addService(ActivityManagerInternal.class, mAmInternal);
@@ -671,6 +688,59 @@
         assertThat(mService.isCurrentProjection(projection)).isTrue();
     }
 
+    @Test
+    public void setContentRecordingSession_successful_notifiesListeners()
+            throws Exception {
+        mService.addCallback(mWatcherCallback);
+        MediaProjectionManagerService.MediaProjection projection = startProjectionPreconditions();
+        projection.start(mIMediaProjectionCallback);
+
+        doReturn(true).when(mWindowManagerInternal).setContentRecordingSession(
+                any(ContentRecordingSession.class));
+        mService.setContentRecordingSession(DISPLAY_SESSION);
+
+        verify(mWatcherCallback).onRecordingSessionSet(
+                projection.getProjectionInfo(),
+                DISPLAY_SESSION
+        );
+    }
+
+    @Test
+    public void setContentRecordingSession_notifiesListenersOnCallbackLooper()
+            throws Exception {
+        mService = new MediaProjectionManagerService(mContext, mTestLooperInjector);
+        mService.addCallback(mWatcherCallback);
+        MediaProjectionManagerService.MediaProjection projection = startProjectionPreconditions();
+        projection.start(mIMediaProjectionCallback);
+        doReturn(true).when(mWindowManagerInternal).setContentRecordingSession(
+                any(ContentRecordingSession.class));
+
+        mService.setContentRecordingSession(DISPLAY_SESSION);
+        // Callback not notified yet, as test looper hasn't dispatched the message yet
+        verify(mWatcherCallback, never()).onRecordingSessionSet(any(), any());
+
+        mTestLooper.dispatchAll();
+        // Message dispatched on test looper. Callback should now be notified.
+        verify(mWatcherCallback).onRecordingSessionSet(
+                projection.getProjectionInfo(),
+                DISPLAY_SESSION
+        );
+    }
+
+    @Test
+    public void setContentRecordingSession_failure_doesNotNotifyListeners()
+            throws Exception {
+        mService.addCallback(mWatcherCallback);
+        MediaProjectionManagerService.MediaProjection projection = startProjectionPreconditions();
+        projection.start(mIMediaProjectionCallback);
+
+        doReturn(false).when(mWindowManagerInternal).setContentRecordingSession(
+                any(ContentRecordingSession.class));
+        mService.setContentRecordingSession(DISPLAY_SESSION);
+
+        verify(mWatcherCallback, never()).onRecordingSessionSet(any(), any());
+    }
+
     private void verifySetSessionWithContent(@ContentRecordingSession.RecordContent int content) {
         verify(mWindowManagerInternal, atLeastOnce()).setContentRecordingSession(
                 mSessionCaptor.capture());
diff --git a/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceCreateProfileTest.java b/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceCreateProfileTest.java
index fdf94be..39cc653 100644
--- a/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceCreateProfileTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceCreateProfileTest.java
@@ -182,7 +182,7 @@
         UserInfo secondaryUser = addUser();
         UserInfo profile = addProfile(secondaryUser);
         // Add the profile it to the users being removed.
-        mUserManagerService.addRemovingUserIdLocked(profile.id);
+        mUserManagerService.addRemovingUserId(profile.id);
         // We should reuse the badge from the profile being removed.
         assertEquals("Badge index not reused while removing a user", 0,
                 mUserManagerService.getFreeProfileBadgeLU(secondaryUser.id,
diff --git a/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceIdRecyclingTest.java b/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceIdRecyclingTest.java
index 1f4c9f8..b6fd65e 100644
--- a/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceIdRecyclingTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceIdRecyclingTest.java
@@ -111,7 +111,7 @@
 
     private void removeUser(int userId) {
         mUserManagerService.removeUserInfo(userId);
-        mUserManagerService.addRemovingUserIdLocked(userId);
+        mUserManagerService.addRemovingUserId(userId);
     }
 
     private void assertNoNextIdAvailable(String message) {
diff --git a/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceTest.java
index 4af0323..592be2d 100644
--- a/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceTest.java
@@ -70,6 +70,9 @@
 
         LocalServices.removeServiceForTest(UserManagerInternal.class);
         mUserManagerService = new UserManagerService(InstrumentationRegistry.getContext());
+        // Put the current user to mUsers. UMS can't find userlist.xml, and fallbackToSingleUserLP.
+        mUserManagerService.putUserInfo(
+                new UserInfo(ActivityManager.getCurrentUser(), "Current User", 0));
 
         restrictionsFile = new File(mContext.getCacheDir(), "restrictions.xml");
         restrictionsFile.delete();
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
index eaf4838..e49b290 100755
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -867,13 +867,18 @@
     }
 
     private NotificationRecord generateNotificationRecord(NotificationChannel channel, int userId) {
+        return generateNotificationRecord(channel, 1, userId);
+    }
+
+    private NotificationRecord generateNotificationRecord(NotificationChannel channel, int id,
+            int userId) {
         if (channel == null) {
             channel = mTestNotificationChannel;
         }
         Notification.Builder nb = new Notification.Builder(mContext, channel.getId())
                 .setContentTitle("foo")
                 .setSmallIcon(android.R.drawable.sym_def_app_icon);
-        StatusBarNotification sbn = new StatusBarNotification(PKG, PKG, 1, "tag", mUid, 0,
+        StatusBarNotification sbn = new StatusBarNotification(PKG, PKG, id, "tag", mUid, 0,
                 nb.build(), new UserHandle(userId), null, 0);
         return new NotificationRecord(mContext, sbn, channel);
     }
@@ -4150,6 +4155,30 @@
     }
 
     @Test
+    public void testSetListenerAccessForUser_grantWithNameTooLong_throws() {
+        UserHandle user = UserHandle.of(mContext.getUserId() + 10);
+        ComponentName c = new ComponentName("com.example.package",
+                com.google.common.base.Strings.repeat("Blah", 150));
+
+        assertThrows(IllegalArgumentException.class,
+                () -> mBinderService.setNotificationListenerAccessGrantedForUser(
+                        c, user.getIdentifier(), /* enabled= */ true, true));
+    }
+
+    @Test
+    public void testSetListenerAccessForUser_revokeWithNameTooLong_okay() throws Exception {
+        UserHandle user = UserHandle.of(mContext.getUserId() + 10);
+        ComponentName c = new ComponentName("com.example.package",
+                com.google.common.base.Strings.repeat("Blah", 150));
+
+        mBinderService.setNotificationListenerAccessGrantedForUser(
+                c, user.getIdentifier(), /* enabled= */ false, true);
+
+        verify(mListeners).setPackageOrComponentEnabled(
+                c.flattenToString(), user.getIdentifier(), true, /* enabled= */ false, true);
+    }
+
+    @Test
     public void testSetAssistantAccessForUser() throws Exception {
         UserInfo ui = new UserInfo();
         ui.id = mContext.getUserId() + 10;
@@ -10710,6 +10739,51 @@
     }
 
     @Test
+    public void testUngroupingAutoSummary_differentUsers() throws Exception {
+        NotificationRecord nr0 =
+                generateNotificationRecord(mTestNotificationChannel, 0, USER_SYSTEM);
+        NotificationRecord nr1 =
+                generateNotificationRecord(mTestNotificationChannel, 1, USER_SYSTEM);
+
+        // add notifications + summary for USER_SYSTEM
+        mService.addNotification(nr0);
+        mService.addNotification(nr1);
+        mService.addNotification(mService.createAutoGroupSummary(nr1.getUserId(),
+                nr1.getSbn().getPackageName(), nr1.getKey(), GroupHelper.BASE_FLAGS));
+
+        // add notifications + summary for USER_ALL
+        NotificationRecord nr0_all =
+                generateNotificationRecord(mTestNotificationChannel, 2, UserHandle.USER_ALL);
+        NotificationRecord nr1_all =
+                generateNotificationRecord(mTestNotificationChannel, 3, UserHandle.USER_ALL);
+
+        mService.addNotification(nr0_all);
+        mService.addNotification(nr1_all);
+        mService.addNotification(mService.createAutoGroupSummary(nr0_all.getUserId(),
+                nr0_all.getSbn().getPackageName(), nr0_all.getKey(), GroupHelper.BASE_FLAGS));
+
+        // cancel both children for USER_ALL
+        mBinderService.cancelNotificationWithTag(PKG, PKG, nr0_all.getSbn().getTag(),
+                nr0_all.getSbn().getId(), UserHandle.USER_ALL);
+        mBinderService.cancelNotificationWithTag(PKG, PKG, nr1_all.getSbn().getTag(),
+                nr1_all.getSbn().getId(), UserHandle.USER_ALL);
+        waitForIdle();
+
+        // group helper would send 'remove summary' event
+        mService.clearAutogroupSummaryLocked(UserHandle.USER_ALL,
+                nr0_all.getSbn().getPackageName());
+        waitForIdle();
+
+        // make sure the right summary was removed
+        assertThat(mService.getNotificationCount(nr0_all.getSbn().getPackageName(),
+                UserHandle.USER_ALL, 0, null)).isEqualTo(0);
+
+        // the USER_SYSTEM notifications + summary were not removed
+        assertThat(mService.getNotificationCount(nr0.getSbn().getPackageName(),
+                USER_SYSTEM, 0, null)).isEqualTo(3);
+    }
+
+    @Test
     public void testStrongAuthTracker_isInLockDownMode() {
         mStrongAuthTracker.setGetStrongAuthForUserReturnValue(
                 STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN);
diff --git a/services/tests/voiceinteractiontests/src/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareLoggingLatencyTest.java b/services/tests/voiceinteractiontests/src/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareLoggingLatencyTest.java
index 6a1674b..63b8e17 100644
--- a/services/tests/voiceinteractiontests/src/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareLoggingLatencyTest.java
+++ b/services/tests/voiceinteractiontests/src/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareLoggingLatencyTest.java
@@ -38,13 +38,16 @@
 import android.os.Process;
 import android.os.RemoteException;
 
+import androidx.test.filters.FlakyTest;
 import androidx.test.platform.app.InstrumentationRegistry;
 
 import com.android.internal.util.FakeLatencyTracker;
 
 import org.junit.After;
 import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
+import org.junit.rules.Timeout;
 import org.junit.runner.RunWith;
 import org.junit.runners.JUnit4;
 import org.mockito.ArgumentCaptor;
@@ -52,8 +55,12 @@
 import org.mockito.MockitoAnnotations;
 
 @RunWith(JUnit4.class)
+@FlakyTest(bugId = 275746222)
 public class SoundTriggerMiddlewareLoggingLatencyTest {
 
+    @Rule
+    public Timeout mGlobalTimeout = Timeout.seconds(30);
+
     private FakeLatencyTracker mLatencyTracker;
     @Mock
     private BatteryStatsInternal mBatteryStatsInternal;
diff --git a/services/tests/wmtests/src/com/android/server/policy/SingleKeyGestureTests.java b/services/tests/wmtests/src/com/android/server/policy/SingleKeyGestureTests.java
index 280afba..3bb86a7 100644
--- a/services/tests/wmtests/src/com/android/server/policy/SingleKeyGestureTests.java
+++ b/services/tests/wmtests/src/com/android/server/policy/SingleKeyGestureTests.java
@@ -316,4 +316,16 @@
         pressKey(KEYCODE_POWER, 0 /* pressTime */);
         assertTrue(mShortPressed.await(mWaitTimeout, TimeUnit.MILLISECONDS));
     }
+
+    // Verify short press should not be triggered if no very long press behavior defined but the
+    // press time exceeded the very long press timeout.
+    @Test
+    public void testTimeoutExceedVeryLongPress() throws InterruptedException {
+        mVeryLongPressOnPowerBehavior = false;
+
+        pressKey(KEYCODE_POWER, mVeryLongPressTime + 50);
+        assertTrue(mLongPressed.await(mWaitTimeout, TimeUnit.MILLISECONDS));
+        assertEquals(mVeryLongPressed.getCount(), 1);
+        assertEquals(mShortPressed.getCount(), 1);
+    }
 }
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
index 5369b93..bdd178b 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
@@ -2046,6 +2046,7 @@
 
         // Once transition starts, rotation is applied and transition shows DC rotating.
         testPlayer.startTransition();
+        waitUntilHandlersIdle();
         assertNotEquals(origRot, dc.getConfiguration().windowConfiguration.getRotation());
         assertNotNull(testPlayer.mLastReady);
         assertTrue(testPlayer.mController.isPlaying());
diff --git a/services/tests/wmtests/src/com/android/server/wm/InsetsPolicyTest.java b/services/tests/wmtests/src/com/android/server/wm/InsetsPolicyTest.java
index 204cbf7..994dcf1 100644
--- a/services/tests/wmtests/src/com/android/server/wm/InsetsPolicyTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/InsetsPolicyTest.java
@@ -31,17 +31,12 @@
 import static android.view.WindowManager.LayoutParams.TYPE_STATUS_BAR;
 import static android.view.WindowManager.LayoutParams.TYPE_STATUS_BAR_SUB_PANEL;
 
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
-
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.Mockito.clearInvocations;
-import static org.mockito.Mockito.doNothing;
 import static org.mockito.Mockito.verify;
 
 import android.app.StatusBarManager;
@@ -268,8 +263,6 @@
         navBar.setHasSurface(true);
         navBarProvider.setServerVisible(true);
         final InsetsPolicy policy = mDisplayContent.getInsetsPolicy();
-        spyOn(policy);
-        doNothing().when(policy).startAnimation(anyBoolean(), any());
 
         // Make both system bars invisible.
         mAppWindow.setRequestedVisibleTypes(
@@ -305,8 +298,6 @@
         addNavigationBar().getControllableInsetProvider().setServerVisible(true);
 
         final InsetsPolicy policy = mDisplayContent.getInsetsPolicy();
-        spyOn(policy);
-        doNothing().when(policy).startAnimation(anyBoolean(), any());
         policy.updateBarControlTarget(mAppWindow);
         policy.showTransient(navigationBars() | statusBars(),
                 true /* isGestureOnSystemBar */);
@@ -341,8 +332,6 @@
         mAppWindow.mAboveInsetsState.addSource(navBarSource);
         mAppWindow.mAboveInsetsState.addSource(statusBarSource);
         final InsetsPolicy policy = mDisplayContent.getInsetsPolicy();
-        spyOn(policy);
-        doNothing().when(policy).startAnimation(anyBoolean(), any());
         policy.updateBarControlTarget(mAppWindow);
         policy.showTransient(navigationBars() | statusBars(),
                 true /* isGestureOnSystemBar */);
@@ -390,8 +379,6 @@
         final WindowState app2 = addWindow(TYPE_APPLICATION, "app");
 
         final InsetsPolicy policy = mDisplayContent.getInsetsPolicy();
-        spyOn(policy);
-        doNothing().when(policy).startAnimation(anyBoolean(), any());
         policy.updateBarControlTarget(app);
         policy.showTransient(navigationBars() | statusBars(),
                 true /* isGestureOnSystemBar */);
diff --git a/services/tests/wmtests/src/com/android/server/wm/LaunchParamsPersisterTests.java b/services/tests/wmtests/src/com/android/server/wm/LaunchParamsPersisterTests.java
index 7cb7c79d..2b19ad9 100644
--- a/services/tests/wmtests/src/com/android/server/wm/LaunchParamsPersisterTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/LaunchParamsPersisterTests.java
@@ -107,6 +107,9 @@
                 InstrumentationRegistry.getInstrumentation().getContext().getCacheDir();
         mFolder = new File(cacheFolder, "launch_params_tests");
         deleteRecursively(mFolder);
+        mFolder.mkdir();
+        mUserFolderGetter.apply(TEST_USER_ID).mkdir();
+        mUserFolderGetter.apply(ALTERNATIVE_USER_ID).mkdir();
 
         mDisplayUniqueId = "test:" + sNextUniqueId++;
         mTestDisplay = new TestDisplayContent.Builder(mAtm, 1000, 1500)
diff --git a/services/tests/wmtests/src/com/android/server/wm/TestDisplayContent.java b/services/tests/wmtests/src/com/android/server/wm/TestDisplayContent.java
index 4639ee0..45ecc3f 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TestDisplayContent.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TestDisplayContent.java
@@ -77,6 +77,10 @@
         spyOn(inputMonitor);
         doNothing().when(inputMonitor).resumeDispatchingLw(any());
 
+        final InsetsPolicy insetsPolicy = getInsetsPolicy();
+        WindowTestsBase.suppressInsetsAnimation(insetsPolicy.getPermanentControlTarget());
+        WindowTestsBase.suppressInsetsAnimation(insetsPolicy.getTransientControlTarget());
+
         // For devices that set the sysprop ro.bootanim.set_orientation_<display_id>
         // See DisplayRotation#readDefaultDisplayRotation for context.
         // Without that, meaning of height and width in context of the tests can be swapped if
diff --git a/services/tests/wmtests/src/com/android/server/wm/TestWindowManagerPolicy.java b/services/tests/wmtests/src/com/android/server/wm/TestWindowManagerPolicy.java
index adf3f39..bd111ad 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TestWindowManagerPolicy.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TestWindowManagerPolicy.java
@@ -233,7 +233,7 @@
     }
 
     @Override
-    public void onKeyguardOccludedChangedLw(boolean occluded, boolean waitAppTransition) {
+    public void onKeyguardOccludedChangedLw(boolean occluded) {
     }
 
     public void setSafeMode(boolean safeMode) {
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java
index 197ee92..64330d8 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java
@@ -81,6 +81,7 @@
 import android.platform.test.annotations.Presubmit;
 import android.util.DisplayMetrics;
 import android.util.MergedConfiguration;
+import android.view.ContentRecordingSession;
 import android.view.IWindow;
 import android.view.IWindowSessionCallback;
 import android.view.InputChannel;
@@ -101,6 +102,7 @@
 
 import com.android.compatibility.common.util.AdoptShellPermissionsRule;
 import com.android.internal.os.IResultReceiver;
+import com.android.server.LocalServices;
 
 import org.junit.Rule;
 import org.junit.Test;
@@ -761,6 +763,63 @@
     }
 
     @Test
+    public void setContentRecordingSession_sessionNull_returnsTrue() {
+        WindowManagerInternal wmInternal = LocalServices.getService(WindowManagerInternal.class);
+
+        boolean result = wmInternal.setContentRecordingSession(/* incomingSession= */ null);
+
+        assertThat(result).isTrue();
+    }
+
+    @Test
+    public void setContentRecordingSession_sessionContentDisplay_returnsTrue() {
+        WindowManagerInternal wmInternal = LocalServices.getService(WindowManagerInternal.class);
+        ContentRecordingSession session = ContentRecordingSession.createDisplaySession(
+                DEFAULT_DISPLAY);
+
+        boolean result = wmInternal.setContentRecordingSession(session);
+
+        assertThat(result).isTrue();
+    }
+
+    @Test
+    public void setContentRecordingSession_sessionContentTask_noMatchingTask_returnsFalse() {
+        WindowManagerInternal wmInternal = LocalServices.getService(WindowManagerInternal.class);
+        IBinder launchCookie = new Binder();
+        ContentRecordingSession session = ContentRecordingSession.createTaskSession(launchCookie);
+
+        boolean result = wmInternal.setContentRecordingSession(session);
+
+        assertThat(result).isFalse();
+    }
+
+    @Test
+    public void setContentRecordingSession_sessionContentTask_matchingTask_returnsTrue() {
+        WindowManagerInternal wmInternal = LocalServices.getService(WindowManagerInternal.class);
+        ActivityRecord activityRecord = createActivityRecord(createTask(mDefaultDisplay));
+        ContentRecordingSession session = ContentRecordingSession.createTaskSession(
+                activityRecord.mLaunchCookie);
+
+        boolean result = wmInternal.setContentRecordingSession(session);
+
+        assertThat(result).isTrue();
+    }
+
+    @Test
+    public void setContentRecordingSession_matchingTask_mutatesSessionWithWindowContainerToken() {
+        WindowManagerInternal wmInternal = LocalServices.getService(WindowManagerInternal.class);
+        Task task = createTask(mDefaultDisplay);
+        ActivityRecord activityRecord = createActivityRecord(task);
+        ContentRecordingSession session = ContentRecordingSession.createTaskSession(
+                activityRecord.mLaunchCookie);
+
+        wmInternal.setContentRecordingSession(session);
+
+        assertThat(session.getTokenToRecord()).isEqualTo(
+                task.mRemoteToken.toWindowContainerToken().asBinder());
+    }
+
+    @Test
     public void testisLetterboxBackgroundMultiColored() {
         assertThat(setupLetterboxConfigurationWithBackgroundType(
                 LETTERBOX_BACKGROUND_APP_COLOR_BACKGROUND_FLOATING)).isTrue();
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
index be8ee78..d5547ec 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
@@ -222,6 +222,10 @@
         displayPolicy.finishWindowsDrawn();
         displayPolicy.finishScreenTurningOn();
 
+        final InsetsPolicy insetsPolicy = mDefaultDisplay.getInsetsPolicy();
+        suppressInsetsAnimation(insetsPolicy.getTransientControlTarget());
+        suppressInsetsAnimation(insetsPolicy.getPermanentControlTarget());
+
         mTransaction = mSystemServicesTestRule.mTransaction;
         mMockSession = mock(Session.class);
 
@@ -278,6 +282,15 @@
         checkDeviceSpecificOverridesNotApplied();
     }
 
+    /**
+     * The test doesn't create real SurfaceControls, but mocked ones. This prevents the target from
+     * controlling them, or it will cause {@link NullPointerException}.
+     */
+    static void suppressInsetsAnimation(InsetsControlTarget target) {
+        spyOn(target);
+        Mockito.doNothing().when(target).notifyInsetsControlChanged();
+    }
+
     @After
     public void tearDown() throws Exception {
         if (mUseFakeSettingsProvider) {
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index dc5b75a..3e79193 100644
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -50,7 +50,6 @@
 import android.telephony.ims.RcsUceAdapter;
 import android.telephony.ims.feature.MmTelFeature;
 import android.telephony.ims.feature.RcsFeature;
-import android.telephony.ims.stub.ImsRegistrationImplBase;
 
 import com.android.internal.telephony.ICarrierConfigLoader;
 import com.android.telephony.Rlog;
@@ -5813,6 +5812,21 @@
          */
         public static final int NR_SA_DISABLE_POLICY_VOWIFI_REGISTERED = 3;
 
+        /**
+         * This specifies whether the carrier support the global number format or not.
+         * {@link SubscriptionManager#getPhoneNumber(int)},
+         * {@link SubscriptionManager#getPhoneNumber(int, int)} with
+         * {@link SubscriptionManager#PHONE_NUMBER_SOURCE_IMS}
+         * In order to provide the phone number to the APIs, the framework extracts the phone
+         * number from the message received from the carrier server. If the carrier does not use
+         * global number format, the framework could not provide phone number.
+         * <p>
+         * If not set or set to false value, the framework handle only global number format URI.
+         * @hide
+         */
+        public static final String KEY_ALLOW_NON_GLOBAL_PHONE_NUMBER_FORMAT_BOOL =
+                KEY_PREFIX + "allow_non_global_phone_number_format_bool";
+
         private Ims() {}
 
         private static PersistableBundle getDefaults() {
@@ -5925,6 +5939,8 @@
             defaults.putString(KEY_IMS_USER_AGENT_STRING,
                                "#MANUFACTURER#_#MODEL#_Android#AV#_#BUILD#");
 
+            defaults.putBoolean(KEY_ALLOW_NON_GLOBAL_PHONE_NUMBER_FORMAT_BOOL, false);
+
             return defaults;
         }
     }
diff --git a/tests/BinaryTransparencyHostTest/src/android/transparency/test/BinaryTransparencyHostTest.java b/tests/BinaryTransparencyHostTest/src/android/transparency/test/BinaryTransparencyHostTest.java
index db36975..346622f 100644
--- a/tests/BinaryTransparencyHostTest/src/android/transparency/test/BinaryTransparencyHostTest.java
+++ b/tests/BinaryTransparencyHostTest/src/android/transparency/test/BinaryTransparencyHostTest.java
@@ -61,8 +61,11 @@
         options.setTestMethodName("testCollectAllApexInfo");
 
         // Collect APEX package names from /apex, then pass them as expectation to be verified.
+        // The package names are collected from the find name with deduplication (NB: we used to
+        // deduplicate by dropping directory names with '@', but there's a DCLA case where it only
+        // has one directory with '@'. So we have to keep it and deduplicate the current way).
         CommandResult result = getDevice().executeShellV2Command(
-                "ls -d /apex/*/ |grep -v @ |grep -v /apex/sharedlibs |cut -d/ -f3");
+                "ls -d /apex/*/ |grep -v /apex/sharedlibs |cut -d/ -f3 |cut -d@ -f1 |sort |uniq");
         assertTrue(result.getStatus() == CommandStatus.SUCCESS);
         String[] packageNames = result.getStdout().split("\n");
         for (var i = 0; i < packageNames.length; i++) {
diff --git a/tests/FlickerTests/Android.bp b/tests/FlickerTests/Android.bp
index 8faedeb..37b529b 100644
--- a/tests/FlickerTests/Android.bp
+++ b/tests/FlickerTests/Android.bp
@@ -37,6 +37,7 @@
     name: "FlickerTestsActivityEmbedding-src",
     srcs: [
         "src/**/activityembedding/*.kt",
+        "src/**/activityembedding/open/*.kt",
         "src/**/activityembedding/close/*.kt",
         "src/**/activityembedding/rotation/*.kt",
     ],
@@ -108,6 +109,7 @@
         ":FlickerTestsAppLaunch-src",
         ":FlickerTestsQuickswitch-src",
         ":FlickerTestsRotation-src",
+        ":FlickerTestsNotification-src",
     ],
 }
 
@@ -148,6 +150,9 @@
         ":FlickerTestsBase-src",
         ":FlickerTestsAppLaunch-src",
     ],
+    exclude_srcs: [
+        ":FlickerTestsActivityEmbedding-src",
+    ],
 }
 
 android_test {
diff --git a/tests/FlickerTests/AndroidTestTemplate.xml b/tests/FlickerTests/AndroidTestTemplate.xml
index 1ede943..ed63ec0 100644
--- a/tests/FlickerTests/AndroidTestTemplate.xml
+++ b/tests/FlickerTests/AndroidTestTemplate.xml
@@ -85,6 +85,8 @@
                 value="/data/user/0/com.android.server.wm.flicker.quickswitch/files"/>
         <option name="directory-keys"
                 value="/data/user/0/com.android.server.wm.flicker.rotation/files"/>
+        <option name="directory-keys"
+                value="/data/user/0/com.android.server.wm.flicker.notification/files"/>
         <option name="collect-on-run-ended-only" value="true"/>
         <option name="clean-up" value="true"/>
     </metrics_collector>
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/CommonAssertions.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/CommonAssertions.kt
index 1fdbe7f..b18a1a8 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/CommonAssertions.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/CommonAssertions.kt
@@ -255,20 +255,18 @@
         invoke("snapshotStartingWindowLayerCoversExactlyOnApp") {
             val snapshotLayers =
                 it.subjects.filter { subject ->
-                    subject.name.contains(ComponentNameMatcher.SNAPSHOT.toLayerName()) &&
+                    ComponentNameMatcher.SNAPSHOT.layerMatchesAnyOf(subject.layer) &&
                         subject.isVisible
                 }
+            val visibleAreas =
+                snapshotLayers
+                    .mapNotNull { snapshotLayer -> snapshotLayer.layer.visibleRegion }
+                    .toTypedArray()
+            val snapshotRegion = RegionSubject(visibleAreas, timestamp)
             // Verify the size of snapshotRegion covers appVisibleRegion exactly in animation.
-            if (snapshotLayers.isNotEmpty()) {
-                val visibleAreas =
-                    snapshotLayers
-                        .mapNotNull { snapshotLayer -> snapshotLayer.layer.visibleRegion }
-                        .toTypedArray()
-                val snapshotRegion = RegionSubject(visibleAreas, timestamp)
+            if (snapshotRegion.region.isNotEmpty) {
                 val appVisibleRegion = it.visibleRegion(component)
-                if (snapshotRegion.region.isNotEmpty) {
-                    snapshotRegion.coversExactly(appVisibleRegion.region)
-                }
+                snapshotRegion.coversExactly(appVisibleRegion.region)
             }
         }
     }
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/close/CloseSecondaryActivityInSplitTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/close/CloseSecondaryActivityInSplitTest.kt
index 7a582f7..4530ef3 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/close/CloseSecondaryActivityInSplitTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/close/CloseSecondaryActivityInSplitTest.kt
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.server.wm.flicker.activityembedding
+package com.android.server.wm.flicker.activityembedding.close
 
 import android.platform.test.annotations.Presubmit
 import android.tools.common.datatypes.Rect
@@ -24,6 +24,7 @@
 import android.tools.device.flicker.legacy.LegacyFlickerTest
 import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
 import androidx.test.filters.RequiresDevice
+import com.android.server.wm.flicker.activityembedding.ActivityEmbeddingTestBase
 import com.android.server.wm.flicker.helpers.ActivityEmbeddingAppHelper
 import org.junit.FixMethodOrder
 import org.junit.Test
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/open/MainActivityStartsSecondaryWithAlwaysExpandTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/open/MainActivityStartsSecondaryWithAlwaysExpandTest.kt
index 48aaebd..0f406fd 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/open/MainActivityStartsSecondaryWithAlwaysExpandTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/open/MainActivityStartsSecondaryWithAlwaysExpandTest.kt
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.server.wm.flicker.activityembedding
+package com.android.server.wm.flicker.activityembedding.open
 
 import android.platform.test.annotations.FlakyTest
 import android.platform.test.annotations.Presubmit
@@ -24,8 +24,10 @@
 import android.tools.device.flicker.legacy.LegacyFlickerTest
 import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
 import androidx.test.filters.RequiresDevice
+import com.android.server.wm.flicker.activityembedding.ActivityEmbeddingTestBase
 import com.android.server.wm.flicker.helpers.ActivityEmbeddingAppHelper
 import org.junit.FixMethodOrder
+import org.junit.Ignore
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.junit.runners.MethodSorters
@@ -54,7 +56,7 @@
             testApp.launchViaIntent(wmHelper)
             testApp.launchSecondaryActivity(wmHelper)
             startDisplayBounds =
-              wmHelper.currentState.layerState.physicalDisplayBounds ?: error("Display not found")
+                wmHelper.currentState.layerState.physicalDisplayBounds ?: error("Display not found")
         }
         transitions {
             // Launch C with alwaysExpand
@@ -66,23 +68,14 @@
         }
     }
 
-    @FlakyTest(bugId = 286952194)
-    @Presubmit
-    @Test
-    override fun navBarWindowIsVisibleAtStartAndEnd() {}
+    @Ignore("Not applicable to this CUJ.") override fun navBarWindowIsVisibleAtStartAndEnd() {}
 
-    @FlakyTest(bugId = 286952194)
-    @Presubmit
-    @Test
-    override fun statusBarWindowIsAlwaysVisible() {}
+    @Ignore("Not applicable to this CUJ.") override fun statusBarWindowIsAlwaysVisible() {}
 
-    @FlakyTest(bugId = 286952194)
-    @Presubmit
-    @Test
-    override fun statusBarLayerPositionAtStartAndEnd() {}
+    @Ignore("Not applicable to this CUJ.") override fun statusBarLayerPositionAtStartAndEnd() {}
 
     /** Transition begins with a split. */
-    @Presubmit
+    @FlakyTest(bugId = 286952194)
     @Test
     fun startsWithSplit() {
         flicker.assertWmStart {
@@ -94,7 +87,7 @@
     }
 
     /** Main activity should become invisible after being covered by always expand activity. */
-    @Presubmit
+    @FlakyTest(bugId = 286952194)
     @Test
     fun mainActivityLayerBecomesInvisible() {
         flicker.assertLayers {
@@ -105,7 +98,7 @@
     }
 
     /** Secondary activity should become invisible after being covered by always expand activity. */
-    @Presubmit
+    @FlakyTest(bugId = 286952194)
     @Test
     fun secondaryActivityLayerBecomesInvisible() {
         flicker.assertLayers {
@@ -116,7 +109,7 @@
     }
 
     /** At the end of transition always expand activity is in fullscreen. */
-    @Presubmit
+    @FlakyTest(bugId = 286952194)
     @Test
     fun endsWithAlwaysExpandActivityCoveringFullScreen() {
         flicker.assertWmEnd {
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/open/OpenActivityEmbeddingPlaceholderSplitTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/open/OpenActivityEmbeddingPlaceholderSplitTest.kt
index 27a5bd0..8a997dd 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/open/OpenActivityEmbeddingPlaceholderSplitTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/open/OpenActivityEmbeddingPlaceholderSplitTest.kt
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.server.wm.flicker.activityembedding
+package com.android.server.wm.flicker.activityembedding.open
 
 import android.platform.test.annotations.Presubmit
 import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
@@ -22,6 +22,7 @@
 import android.tools.device.flicker.legacy.LegacyFlickerTest
 import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
 import androidx.test.filters.RequiresDevice
+import com.android.server.wm.flicker.activityembedding.ActivityEmbeddingTestBase
 import com.android.server.wm.flicker.helpers.ActivityEmbeddingAppHelper
 import org.junit.FixMethodOrder
 import org.junit.Test
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/open/OpenActivityEmbeddingSecondaryToSplitTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/open/OpenActivityEmbeddingSecondaryToSplitTest.kt
index 16dbfce..49aa84b 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/open/OpenActivityEmbeddingSecondaryToSplitTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/open/OpenActivityEmbeddingSecondaryToSplitTest.kt
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.server.wm.flicker.activityembedding
+package com.android.server.wm.flicker.activityembedding.open
 
 import android.platform.test.annotations.Presubmit
 import android.tools.common.traces.component.ComponentNameMatcher
@@ -23,6 +23,7 @@
 import android.tools.device.flicker.legacy.LegacyFlickerTest
 import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
 import androidx.test.filters.RequiresDevice
+import com.android.server.wm.flicker.activityembedding.ActivityEmbeddingTestBase
 import com.android.server.wm.flicker.helpers.ActivityEmbeddingAppHelper
 import org.junit.FixMethodOrder
 import org.junit.Test
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/open/OpenThirdActivityOverSplitTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/open/OpenThirdActivityOverSplitTest.kt
new file mode 100644
index 0000000..27de12e
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/open/OpenThirdActivityOverSplitTest.kt
@@ -0,0 +1,166 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm.flicker.activityembedding.open
+
+import android.platform.test.annotations.Presubmit
+import android.tools.common.datatypes.Rect
+import android.tools.common.traces.component.ComponentNameMatcher
+import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
+import android.tools.device.flicker.legacy.FlickerBuilder
+import android.tools.device.flicker.legacy.LegacyFlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
+import androidx.test.filters.RequiresDevice
+import com.android.server.wm.flicker.activityembedding.ActivityEmbeddingTestBase
+import com.android.server.wm.flicker.helpers.ActivityEmbeddingAppHelper
+import org.junit.FixMethodOrder
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+
+/**
+ * Test launching a secondary activity over an existing split. By default the new secondary activity
+ * will stack over the previous secondary activity.
+ *
+ * Setup: From Activity A launch a split A|B.
+ *
+ * Transitions: Let B start C, expect C to cover B and end up in split A|C.
+ *
+ * To run this test: `atest FlickerTests:OpenThirdActivityOverSplitTest`
+ */
+@RequiresDevice
+@RunWith(Parameterized::class)
+@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+class OpenThirdActivityOverSplitTest(flicker: LegacyFlickerTest) :
+    ActivityEmbeddingTestBase(flicker) {
+    /** {@inheritDoc} */
+    override val transition: FlickerBuilder.() -> Unit = {
+        setup {
+            tapl.setExpectedRotationCheckEnabled(false)
+            // Launch a split.
+            testApp.launchViaIntent(wmHelper)
+            testApp.launchSecondaryActivity(wmHelper)
+
+            startDisplayBounds =
+                wmHelper.currentState.layerState.physicalDisplayBounds
+                    ?: error("Can't get display bounds")
+        }
+        transitions { testApp.launchThirdActivity(wmHelper) }
+        teardown {
+            tapl.goHome()
+            testApp.exit(wmHelper)
+        }
+    }
+
+    /** Main activity remains visible throughout the transition. */
+    @Presubmit
+    @Test
+    fun mainActivityWindowAlwaysVisible() {
+        flicker.assertWm { isAppWindowVisible(ActivityEmbeddingAppHelper.MAIN_ACTIVITY_COMPONENT) }
+    }
+
+    /** Main activity remains visible throughout the transition and takes up half of the screen. */
+    @Presubmit
+    @Test
+    fun mainActivityLayersAlwaysVisible() {
+        flicker.assertLayers { isVisible(ActivityEmbeddingAppHelper.MAIN_ACTIVITY_COMPONENT) }
+
+        flicker.assertLayersStart {
+            val display =
+                this.entry.displays.firstOrNull { it.isOn && !it.isVirtual }
+                    ?: error("No non-virtual and on display found")
+            val mainActivityRegion =
+                this.visibleRegion(ActivityEmbeddingAppHelper.MAIN_ACTIVITY_COMPONENT)
+            val secondaryActivityRegion =
+                this.visibleRegion(ActivityEmbeddingAppHelper.SECONDARY_ACTIVITY_COMPONENT).region
+            mainActivityRegion.plus(secondaryActivityRegion).coversExactly(display.layerStackSpace)
+        }
+
+        flicker.assertLayersEnd {
+            val display =
+                this.entry.displays.firstOrNull { it.isOn && !it.isVirtual }
+                    ?: error("No non-virtual and on display found")
+            val mainActivityRegion =
+                this.visibleRegion(ActivityEmbeddingAppHelper.MAIN_ACTIVITY_COMPONENT)
+            val secondaryActivityRegion =
+                this.visibleRegion(ActivityEmbeddingAppHelper.SECONDARY_ACTIVITY_COMPONENT)
+            secondaryActivityRegion.isEmpty()
+            val thirdActivityRegion =
+                this.visibleRegion(ActivityEmbeddingAppHelper.THIRD_ACTIVITY_COMPONENT)
+            mainActivityRegion
+                .plus(thirdActivityRegion.region)
+                .coversExactly(display.layerStackSpace)
+        }
+    }
+
+    /** Third activity launches during the transition and covers up secondary activity. */
+    @Presubmit
+    @Test
+    fun thirdActivityWindowLaunchesIntoSplit() {
+        flicker.assertWm {
+            isAppWindowVisible(ActivityEmbeddingAppHelper.SECONDARY_ACTIVITY_COMPONENT)
+                .isAppWindowInvisible(ActivityEmbeddingAppHelper.THIRD_ACTIVITY_COMPONENT)
+                .then()
+                .isAppWindowVisible(ActivityEmbeddingAppHelper.SECONDARY_ACTIVITY_COMPONENT)
+                .isAppWindowVisible(ActivityEmbeddingAppHelper.THIRD_ACTIVITY_COMPONENT)
+                .then()
+                .isAppWindowVisible(ActivityEmbeddingAppHelper.THIRD_ACTIVITY_COMPONENT)
+                .isAppWindowInvisible(
+                    ActivityEmbeddingAppHelper.SECONDARY_ACTIVITY_COMPONENT
+                ) // expectation
+        }
+    }
+
+    /** Third activity launches during the transition and covers up secondary activity. */
+    @Presubmit
+    @Test
+    fun thirdActivityLayerLaunchesIntoSplit() {
+        flicker.assertLayers {
+            isVisible(ActivityEmbeddingAppHelper.SECONDARY_ACTIVITY_COMPONENT)
+                .isInvisible(ActivityEmbeddingAppHelper.THIRD_ACTIVITY_COMPONENT)
+                .then()
+                .isVisible(ActivityEmbeddingAppHelper.SECONDARY_ACTIVITY_COMPONENT)
+                .isVisible(ActivityEmbeddingAppHelper.THIRD_ACTIVITY_COMPONENT)
+                .then()
+                .isVisible(ActivityEmbeddingAppHelper.THIRD_ACTIVITY_COMPONENT)
+                .isInvisible(ActivityEmbeddingAppHelper.SECONDARY_ACTIVITY_COMPONENT)
+        }
+    }
+
+    /** Assert the background animation layer is never visible during transition. */
+    @Presubmit
+    @Test
+    fun backgroundLayerNeverVisible() {
+        val backgroundColorLayer = ComponentNameMatcher("", "Animation Background")
+        flicker.assertLayers { isInvisible(backgroundColorLayer) }
+    }
+
+    companion object {
+        /** {@inheritDoc} */
+        private var startDisplayBounds = Rect.EMPTY
+        /**
+         * Creates the test configurations.
+         *
+         * See [LegacyFlickerTestFactory.nonRotationTests] for configuring screen orientation and
+         * navigation modes.
+         */
+        @Parameterized.Parameters(name = "{0}")
+        @JvmStatic
+        fun getParams() = LegacyFlickerTestFactory.nonRotationTests()
+    }
+}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/rotation/RotateSplitNoChangeTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/rotation/RotateSplitNoChangeTest.kt
index 856c9e2..da56500 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/rotation/RotateSplitNoChangeTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/rotation/RotateSplitNoChangeTest.kt
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.server.wm.flicker.activityembedding
+package com.android.server.wm.flicker.activityembedding.rotation
 
 import android.platform.test.annotations.Presubmit
 import android.tools.common.traces.component.ComponentNameMatcher
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppBackButtonTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppBackButtonTest.kt
index 865d5b4..dbbc771 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppBackButtonTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppBackButtonTest.kt
@@ -17,7 +17,7 @@
 package com.android.server.wm.flicker.close
 
 import android.platform.test.annotations.FlakyTest
-import android.tools.device.flicker.annotation.FlickerServiceCompatible
+import android.tools.common.flicker.annotation.FlickerServiceCompatible
 import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
 import android.tools.device.flicker.legacy.FlickerBuilder
 import android.tools.device.flicker.legacy.LegacyFlickerTest
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppBackButtonTestCfArm.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppBackButtonTestCfArm.kt
index c108633..566f393 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppBackButtonTestCfArm.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppBackButtonTestCfArm.kt
@@ -16,7 +16,7 @@
 
 package com.android.server.wm.flicker.close
 
-import android.tools.device.flicker.annotation.FlickerServiceCompatible
+import android.tools.common.flicker.annotation.FlickerServiceCompatible
 import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
 import android.tools.device.flicker.legacy.LegacyFlickerTest
 import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppHomeButtonTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppHomeButtonTest.kt
index ea9710c6..ed930fc 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppHomeButtonTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppHomeButtonTest.kt
@@ -17,7 +17,7 @@
 package com.android.server.wm.flicker.close
 
 import android.platform.test.annotations.FlakyTest
-import android.tools.device.flicker.annotation.FlickerServiceCompatible
+import android.tools.common.flicker.annotation.FlickerServiceCompatible
 import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
 import android.tools.device.flicker.legacy.FlickerBuilder
 import android.tools.device.flicker.legacy.LegacyFlickerTest
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppHomeButtonTestCfArm.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppHomeButtonTestCfArm.kt
index d65555a..49ed183 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppHomeButtonTestCfArm.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppHomeButtonTestCfArm.kt
@@ -16,7 +16,7 @@
 
 package com.android.server.wm.flicker.close
 
-import android.tools.device.flicker.annotation.FlickerServiceCompatible
+import android.tools.common.flicker.annotation.FlickerServiceCompatible
 import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
 import android.tools.device.flicker.legacy.LegacyFlickerTest
 import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ActivityEmbeddingAppHelper.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ActivityEmbeddingAppHelper.kt
index 351eb1e..ced7a1e 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ActivityEmbeddingAppHelper.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ActivityEmbeddingAppHelper.kt
@@ -59,6 +59,23 @@
             .waitForAndVerify()
     }
 
+    /** Clicks the button to launch a third activity over a secondary activity. */
+    fun launchThirdActivity(wmHelper: WindowManagerStateHelper) {
+        val launchButton =
+            uiDevice.wait(
+                Until.findObject(By.res(getPackage(), "launch_third_activity_button")),
+                FIND_TIMEOUT
+            )
+        require(launchButton != null) { "Can't find launch third activity button on screen." }
+        launchButton.click()
+        wmHelper
+            .StateSyncBuilder()
+            .withActivityState(MAIN_ACTIVITY_COMPONENT, PlatformConsts.STATE_RESUMED)
+            .withActivityState(SECONDARY_ACTIVITY_COMPONENT, PlatformConsts.STATE_STOPPED)
+            .withActivityState(THIRD_ACTIVITY_COMPONENT, PlatformConsts.STATE_RESUMED)
+            .waitForAndVerify()
+    }
+
     /**
      * Clicks the button to finishes the secondary activity launched through
      * [launchSecondaryActivity], waits for the main activity to resume.
@@ -166,6 +183,9 @@
         val SECONDARY_ACTIVITY_COMPONENT =
             ActivityOptions.ActivityEmbedding.SecondaryActivity.COMPONENT.toFlickerComponent()
 
+        val THIRD_ACTIVITY_COMPONENT =
+            ActivityOptions.ActivityEmbedding.ThirdActivity.COMPONENT.toFlickerComponent()
+
         val ALWAYS_EXPAND_ACTIVITY_COMPONENT =
             ActivityOptions.ActivityEmbedding.AlwaysExpandActivity.COMPONENT.toFlickerComponent()
 
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/PipAppHelper.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/PipAppHelper.kt
index 24e231c..c975a50 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/PipAppHelper.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/PipAppHelper.kt
@@ -250,10 +250,13 @@
             waitConditions = arrayOf(ConditionsFactory.hasPipWindow())
         )
 
+        val windowRegion = wmHelper.getWindowRegion(this)
+
         wmHelper
             .StateSyncBuilder()
             .withWindowSurfaceAppeared(this)
             .withPipShown()
+            .withSurfaceVisibleRegion(this, windowRegion)
             .waitForAndVerify()
     }
 
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromIconColdTestCfArm.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromIconColdTestCfArm.kt
index 2563bfb..4fd4a61 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromIconColdTestCfArm.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromIconColdTestCfArm.kt
@@ -17,7 +17,7 @@
 package com.android.server.wm.flicker.launch
 
 import android.platform.test.annotations.FlakyTest
-import android.tools.device.flicker.annotation.FlickerServiceCompatible
+import android.tools.common.flicker.annotation.FlickerServiceCompatible
 import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
 import android.tools.device.flicker.legacy.LegacyFlickerTest
 import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromIntentColdTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromIntentColdTest.kt
index 33302fa..e39a578 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromIntentColdTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromIntentColdTest.kt
@@ -18,7 +18,7 @@
 
 import android.platform.test.annotations.Postsubmit
 import android.platform.test.annotations.Presubmit
-import android.tools.device.flicker.annotation.FlickerServiceCompatible
+import android.tools.common.flicker.annotation.FlickerServiceCompatible
 import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
 import android.tools.device.flicker.legacy.FlickerBuilder
 import android.tools.device.flicker.legacy.LegacyFlickerTest
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromIntentColdTestCfArm.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromIntentColdTestCfArm.kt
index 45fb453..6d0b6f4 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromIntentColdTestCfArm.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromIntentColdTestCfArm.kt
@@ -17,7 +17,7 @@
 package com.android.server.wm.flicker.launch
 
 import android.platform.test.annotations.FlakyTest
-import android.tools.device.flicker.annotation.FlickerServiceCompatible
+import android.tools.common.flicker.annotation.FlickerServiceCompatible
 import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
 import android.tools.device.flicker.legacy.LegacyFlickerTest
 import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromIntentWarmTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromIntentWarmTest.kt
index 12ee7d0..d2c3807 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromIntentWarmTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromIntentWarmTest.kt
@@ -18,7 +18,7 @@
 
 import android.platform.test.annotations.FlakyTest
 import android.platform.test.annotations.Presubmit
-import android.tools.device.flicker.annotation.FlickerServiceCompatible
+import android.tools.common.flicker.annotation.FlickerServiceCompatible
 import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
 import android.tools.device.flicker.legacy.FlickerBuilder
 import android.tools.device.flicker.legacy.LegacyFlickerTest
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromIntentWarmTestCfArm.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromIntentWarmTestCfArm.kt
index 1371fd7..3e0958a 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromIntentWarmTestCfArm.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromIntentWarmTestCfArm.kt
@@ -16,7 +16,7 @@
 
 package com.android.server.wm.flicker.launch
 
-import android.tools.device.flicker.annotation.FlickerServiceCompatible
+import android.tools.common.flicker.annotation.FlickerServiceCompatible
 import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
 import android.tools.device.flicker.legacy.LegacyFlickerTest
 import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockscreenViaIntentTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockscreenViaIntentTest.kt
index 46eb257..7a16060 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockscreenViaIntentTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockscreenViaIntentTest.kt
@@ -20,8 +20,8 @@
 import android.platform.test.annotations.Presubmit
 import android.tools.common.NavBar
 import android.tools.common.Rotation
+import android.tools.common.flicker.annotation.FlickerServiceCompatible
 import android.tools.common.traces.component.ComponentNameMatcher
-import android.tools.device.flicker.annotation.FlickerServiceCompatible
 import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
 import android.tools.device.flicker.legacy.LegacyFlickerTest
 import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromOverviewTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromOverviewTest.kt
index 1497e50..eec6bfd 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromOverviewTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromOverviewTest.kt
@@ -19,7 +19,7 @@
 import android.platform.test.annotations.FlakyTest
 import android.platform.test.annotations.Presubmit
 import android.tools.common.Rotation
-import android.tools.device.flicker.annotation.FlickerServiceCompatible
+import android.tools.common.flicker.annotation.FlickerServiceCompatible
 import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
 import android.tools.device.flicker.legacy.FlickerBuilder
 import android.tools.device.flicker.legacy.LegacyFlickerTest
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromOverviewTestCfArm.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromOverviewTestCfArm.kt
index 9b6c136..ab6a1ea 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromOverviewTestCfArm.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromOverviewTestCfArm.kt
@@ -16,7 +16,7 @@
 
 package com.android.server.wm.flicker.launch
 
-import android.tools.device.flicker.annotation.FlickerServiceCompatible
+import android.tools.common.flicker.annotation.FlickerServiceCompatible
 import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
 import android.tools.device.flicker.legacy.LegacyFlickerTest
 import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenCameraFromHomeOnDoubleClickPowerButtonTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenCameraFromHomeOnDoubleClickPowerButtonTest.kt
index 4a1bd7e..1bdb6e71 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenCameraFromHomeOnDoubleClickPowerButtonTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenCameraFromHomeOnDoubleClickPowerButtonTest.kt
@@ -18,6 +18,8 @@
 
 import android.os.SystemClock
 import android.platform.test.annotations.Postsubmit
+import android.tools.common.flicker.subject.layers.LayersTraceSubject
+import android.tools.common.traces.component.ComponentNameMatcher
 import android.tools.device.apphelpers.CameraAppHelper
 import android.tools.device.apphelpers.StandardAppHelper
 import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
@@ -137,8 +139,14 @@
 
     @Postsubmit
     @Test
-    override fun visibleLayersShownMoreThanOneConsecutiveEntry() =
-        super.visibleLayersShownMoreThanOneConsecutiveEntry()
+    override fun visibleLayersShownMoreThanOneConsecutiveEntry() {
+        flicker.assertLayers {
+            this.visibleLayersShownMoreThanOneConsecutiveEntry(
+                LayersTraceSubject.VISIBLE_FOR_MORE_THAN_ONE_ENTRY_IGNORE_LAYERS +
+                    listOf(CAMERA_BACKGROUND)
+            )
+        }
+    }
 
     @Postsubmit
     @Test
@@ -161,5 +169,12 @@
         @Parameterized.Parameters(name = "{0}")
         @JvmStatic
         fun getParams() = LegacyFlickerTestFactory.nonRotationTests()
+
+        private val CAMERA_BACKGROUND =
+            ComponentNameMatcher(
+                "Background for SurfaceView" +
+                    "[com.google.android.GoogleCamera/" +
+                    "com.google.android.apps.camera.legacy.app.activity.main.CameraActivity]"
+            )
     }
 }
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/notification/OpenAppFromLockscreenNotificationWithOverlayAppTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/notification/OpenAppFromLockscreenNotificationWithOverlayAppTest.kt
index c336399..4e8a697 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/notification/OpenAppFromLockscreenNotificationWithOverlayAppTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/notification/OpenAppFromLockscreenNotificationWithOverlayAppTest.kt
@@ -28,6 +28,7 @@
 import androidx.test.filters.RequiresDevice
 import com.android.server.wm.flicker.helpers.ShowWhenLockedAppHelper
 import org.junit.FixMethodOrder
+import org.junit.Ignore
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.junit.runners.MethodSorters
@@ -106,9 +107,9 @@
         super.visibleLayersShownMoreThanOneConsecutiveEntry()
 
     /** {@inheritDoc} */
-    @FlakyTest(bugId = 209599395)
     @Test
-    override fun navBarLayerIsVisibleAtStartAndEnd() = super.navBarLayerIsVisibleAtStartAndEnd()
+    @Ignore("Not applicable to this CUJ. Display starts off and app is full screen at the end")
+    override fun navBarLayerIsVisibleAtStartAndEnd() {}
 
     /** {@inheritDoc} */
     @Presubmit
diff --git a/tests/FlickerTests/test-apps/flickerapp/AndroidManifest.xml b/tests/FlickerTests/test-apps/flickerapp/AndroidManifest.xml
index 6430283..7a2e74b 100644
--- a/tests/FlickerTests/test-apps/flickerapp/AndroidManifest.xml
+++ b/tests/FlickerTests/test-apps/flickerapp/AndroidManifest.xml
@@ -200,6 +200,13 @@
             android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout"
             android:exported="false"/>
         <activity
+            android:name=".ActivityEmbeddingThirdActivity"
+            android:label="ActivityEmbedding Third"
+            android:taskAffinity="com.android.server.wm.flicker.testapp.ActivityEmbedding"
+            android:theme="@style/CutoutShortEdges"
+            android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout"
+            android:exported="false"/>
+        <activity
             android:name=".ActivityEmbeddingAlwaysExpandActivity"
             android:label="ActivityEmbedding AlwaysExpand"
             android:taskAffinity="com.android.server.wm.flicker.testapp.ActivityEmbedding"
diff --git a/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_embedding_secondary_activity_layout.xml b/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_embedding_secondary_activity_layout.xml
index 239aba5..6731446 100644
--- a/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_embedding_secondary_activity_layout.xml
+++ b/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_embedding_secondary_activity_layout.xml
@@ -27,4 +27,12 @@
       android:layout_height="48dp"
       android:text="Finish" />
 
+  <Button
+      android:id="@+id/launch_third_activity_button"
+      android:layout_width="wrap_content"
+      android:layout_height="48dp"
+      android:layout_centerHorizontal="true"
+      android:onClick="launchThirdActivity"
+      android:text="Launch a third activity" />
+
 </LinearLayout>
\ No newline at end of file
diff --git a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityEmbeddingSecondaryActivity.java b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityEmbeddingSecondaryActivity.java
index 6e78750..dc21027 100644
--- a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityEmbeddingSecondaryActivity.java
+++ b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityEmbeddingSecondaryActivity.java
@@ -17,6 +17,7 @@
 package com.android.server.wm.flicker.testapp;
 
 import android.app.Activity;
+import android.content.Intent;
 import android.graphics.Color;
 import android.os.Bundle;
 import android.view.View;
@@ -40,4 +41,9 @@
                     }
             });
     }
+
+    public void launchThirdActivity(View view) {
+        startActivity(new Intent().setComponent(
+                ActivityOptions.ActivityEmbedding.ThirdActivity.COMPONENT));
+    }
 }
diff --git a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityEmbeddingThirdActivity.java b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityEmbeddingThirdActivity.java
new file mode 100644
index 0000000..3bd7281
--- /dev/null
+++ b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityEmbeddingThirdActivity.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm.flicker.testapp;
+
+import android.app.Activity;
+import android.graphics.Color;
+import android.os.Bundle;
+
+/**
+ * Activity to be used also as a secondary activity to split with
+ * {@link ActivityEmbeddingMainActivity}.
+ */
+public class ActivityEmbeddingThirdActivity extends Activity {
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.activity_embedding_base_layout);
+        findViewById(R.id.root_activity_layout).setBackgroundColor(Color.RED);
+    }
+}
diff --git a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityOptions.java b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityOptions.java
index 5210618..d84ac42 100644
--- a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityOptions.java
+++ b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityOptions.java
@@ -99,6 +99,12 @@
                     FLICKER_APP_PACKAGE + ".ActivityEmbeddingSecondaryActivity");
         }
 
+        public static class ThirdActivity {
+            public static final String LABEL = "ActivityEmbeddingThirdActivity";
+            public static final ComponentName COMPONENT = new ComponentName(FLICKER_APP_PACKAGE,
+                    FLICKER_APP_PACKAGE + ".ActivityEmbeddingThirdActivity");
+        }
+
         public static class AlwaysExpandActivity {
             public static final String LABEL = "ActivityEmbeddingAlwaysExpandActivity";
             public static final ComponentName COMPONENT = new ComponentName(FLICKER_APP_PACKAGE,
diff --git a/tests/UiBench/Android.bp b/tests/UiBench/Android.bp
index 90e61c5..0d2f2ef 100644
--- a/tests/UiBench/Android.bp
+++ b/tests/UiBench/Android.bp
@@ -24,6 +24,5 @@
         "androidx.recyclerview_recyclerview",
         "androidx.leanback_leanback",
     ],
-    certificate: "platform",
     test_suites: ["device-tests"],
 }
diff --git a/tests/UiBench/AndroidManifest.xml b/tests/UiBench/AndroidManifest.xml
index 47211c5..4fc6ec7 100644
--- a/tests/UiBench/AndroidManifest.xml
+++ b/tests/UiBench/AndroidManifest.xml
@@ -18,7 +18,6 @@
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
      xmlns:tools="http://schemas.android.com/tools"
      package="com.android.test.uibench">
-    <uses-permission android:name="android.permission.INJECT_EVENTS" />
 
     <application android:allowBackup="false"
          android:theme="@style/Theme.AppCompat.Light.DarkActionBar"
diff --git a/tests/UiBench/src/com/android/test/uibench/EditTextTypeActivity.java b/tests/UiBench/src/com/android/test/uibench/EditTextTypeActivity.java
index 1b2c3c6..06b65a7 100644
--- a/tests/UiBench/src/com/android/test/uibench/EditTextTypeActivity.java
+++ b/tests/UiBench/src/com/android/test/uibench/EditTextTypeActivity.java
@@ -15,15 +15,11 @@
  */
 package com.android.test.uibench;
 
-import android.app.Instrumentation;
+import android.content.Intent;
 import android.os.Bundle;
-import android.os.Looper;
-import android.os.MessageQueue;
-import androidx.appcompat.app.AppCompatActivity;
-import android.view.KeyEvent;
 import android.widget.EditText;
 
-import java.util.concurrent.Semaphore;
+import androidx.appcompat.app.AppCompatActivity;
 
 /**
  * Note: currently incomplete, complexity of input continuously grows, instead of looping
@@ -32,7 +28,13 @@
  * Simulates typing continuously into an EditText.
  */
 public class EditTextTypeActivity extends AppCompatActivity {
-    Thread mThread;
+
+    /**
+     * Broadcast action: Used to notify UiBenchEditTextTypingMicrobenchmark test when the
+     * test activity was paused.
+     */
+    private static final String ACTION_CANCEL_TYPING_CALLBACK =
+            "com.android.uibench.action.CANCEL_TYPING_CALLBACK";
 
     private static String sSeedText = "";
     static {
@@ -46,9 +48,6 @@
         sSeedText = builder.toString();
     }
 
-    final Object mLock = new Object();
-    boolean mShouldStop = false;
-
     @Override
     protected void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
@@ -56,55 +55,13 @@
         EditText editText = new EditText(this);
         editText.setText(sSeedText);
         setContentView(editText);
-
-        final Instrumentation instrumentation = new Instrumentation();
-        final Semaphore sem = new Semaphore(0);
-        MessageQueue.IdleHandler handler = new MessageQueue.IdleHandler() {
-            @Override
-            public boolean queueIdle() {
-                // TODO: consider other signaling approaches
-                sem.release();
-                return true;
-            }
-        };
-        Looper.myQueue().addIdleHandler(handler);
-        synchronized (mLock) {
-            mShouldStop = false;
-        }
-        mThread = new Thread(new Runnable() {
-            int codes[] = { KeyEvent.KEYCODE_H, KeyEvent.KEYCODE_E, KeyEvent.KEYCODE_L,
-                    KeyEvent.KEYCODE_L, KeyEvent.KEYCODE_O, KeyEvent.KEYCODE_SPACE };
-            int i = 0;
-            @Override
-            public void run() {
-                while (true) {
-                    try {
-                        sem.acquire();
-                    } catch (InterruptedException e) {
-                        // TODO, maybe
-                    }
-                    int code = codes[i % codes.length];
-                    if (i % 100 == 99) code = KeyEvent.KEYCODE_ENTER;
-
-                    synchronized (mLock) {
-                        if (mShouldStop) break;
-                    }
-
-                    // TODO: bit of a race here, since the event can arrive after pause/stop.
-                    // (Can't synchronize on key send, since it's synchronous.)
-                    instrumentation.sendKeyDownUpSync(code);
-                    i++;
-                }
-            }
-        });
-        mThread.start();
     }
 
     @Override
     protected void onPause() {
-        synchronized (mLock) {
-            mShouldStop = true;
-        }
+        // Cancel the typing when the test activity was paused.
+        sendBroadcast(new Intent(ACTION_CANCEL_TYPING_CALLBACK).addFlags(
+                Intent.FLAG_RECEIVER_FOREGROUND | Intent.FLAG_RECEIVER_REGISTERED_ONLY));
         super.onPause();
     }
 }