Merge "Sync transform of mirrored surface with the source" into udc-qpr-dev
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index 8d2394b..44068dd 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -2171,6 +2171,10 @@
             }
         }
 
+        private void visitUris(@NonNull Consumer<Uri> visitor) {
+            visitIconUri(visitor, getIcon());
+        }
+
         @Override
         public Action clone() {
             return new Action(
@@ -2858,7 +2862,7 @@
 
         if (actions != null) {
             for (Action action : actions) {
-                visitIconUri(visitor, action.getIcon());
+                action.visitUris(visitor);
             }
         }
 
@@ -2939,6 +2943,11 @@
         if (mBubbleMetadata != null) {
             visitIconUri(visitor, mBubbleMetadata.getIcon());
         }
+
+        if (extras != null && extras.containsKey(WearableExtender.EXTRA_WEARABLE_EXTENSIONS)) {
+            WearableExtender extender = new WearableExtender(this);
+            extender.visitUris(visitor);
+        }
     }
 
     /**
@@ -3038,10 +3047,9 @@
         // cannot look into the extras as there may be parcelables there that
         // the platform does not know how to handle. To go around that we have
         // an explicit list of the pending intents in the extras bundle.
-        final boolean collectPendingIntents = (allPendingIntents == null);
-        if (collectPendingIntents) {
-            PendingIntent.setOnMarshaledListener(
-                    (PendingIntent intent, Parcel out, int outFlags) -> {
+        PendingIntent.OnMarshaledListener addedListener = null;
+        if (allPendingIntents == null) {
+            addedListener = (PendingIntent intent, Parcel out, int outFlags) -> {
                 if (parcel == out) {
                     synchronized (this) {
                         if (allPendingIntents == null) {
@@ -3050,7 +3058,8 @@
                         allPendingIntents.add(intent);
                     }
                 }
-            });
+            };
+            PendingIntent.addOnMarshaledListener(addedListener);
         }
         try {
             // IMPORTANT: Add marshaling code in writeToParcelImpl as we
@@ -3061,8 +3070,8 @@
                 parcel.writeArraySet(allPendingIntents);
             }
         } finally {
-            if (collectPendingIntents) {
-                PendingIntent.setOnMarshaledListener(null);
+            if (addedListener != null) {
+                PendingIntent.removeOnMarshaledListener(addedListener);
             }
         }
     }
@@ -3468,8 +3477,11 @@
      *
      * @hide
      */
-    public void setAllowlistToken(@Nullable IBinder token) {
-        mAllowlistToken = token;
+    public void clearAllowlistToken() {
+        mAllowlistToken = null;
+        if (publicVersion != null) {
+            publicVersion.clearAllowlistToken();
+        }
     }
 
     /**
@@ -11715,6 +11727,12 @@
                 mFlags &= ~mask;
             }
         }
+
+        private void visitUris(@NonNull Consumer<Uri> visitor) {
+            for (Action action : mActions) {
+                action.visitUris(visitor);
+            }
+        }
     }
 
     /**
diff --git a/core/java/android/app/PendingIntent.java b/core/java/android/app/PendingIntent.java
index 705b5ee..c7522b4 100644
--- a/core/java/android/app/PendingIntent.java
+++ b/core/java/android/app/PendingIntent.java
@@ -64,6 +64,7 @@
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
+import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
 import java.util.Objects;
@@ -391,11 +392,12 @@
         void onMarshaled(PendingIntent intent, Parcel parcel, int flags);
     }
 
-    private static final ThreadLocal<OnMarshaledListener> sOnMarshaledListener
-            = new ThreadLocal<>();
+    private static final ThreadLocal<List<OnMarshaledListener>> sOnMarshaledListener =
+            ThreadLocal.withInitial(ArrayList::new);
 
     /**
-     * Registers an listener for pending intents being written to a parcel.
+     * Registers an listener for pending intents being written to a parcel. This replaces any
+     * listeners previously added.
      *
      * @param listener The listener, null to clear.
      *
@@ -403,7 +405,27 @@
      */
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
     public static void setOnMarshaledListener(OnMarshaledListener listener) {
-        sOnMarshaledListener.set(listener);
+        final List<OnMarshaledListener> listeners = sOnMarshaledListener.get();
+        listeners.clear();
+        if (listener != null) {
+            listeners.add(listener);
+        }
+    }
+
+    /**
+     * Adds a listener for pending intents being written to a parcel.
+     * @hide
+     */
+    static void addOnMarshaledListener(OnMarshaledListener listener) {
+        sOnMarshaledListener.get().add(listener);
+    }
+
+    /**
+     * Removes a listener for pending intents being written to a parcel.
+     * @hide
+     */
+    static void removeOnMarshaledListener(OnMarshaledListener listener) {
+        sOnMarshaledListener.get().remove(listener);
     }
 
     private static void checkPendingIntent(int flags, @NonNull Intent intent,
@@ -1451,11 +1473,11 @@
 
     public void writeToParcel(Parcel out, int flags) {
         out.writeStrongBinder(mTarget.asBinder());
-        OnMarshaledListener listener = sOnMarshaledListener.get();
-        if (listener != null) {
-            listener.onMarshaled(this, out, flags);
+        final List<OnMarshaledListener> listeners = sOnMarshaledListener.get();
+        final int numListeners = listeners.size();
+        for (int i = 0; i < numListeners; i++) {
+            listeners.get(i).onMarshaled(this, out, flags);
         }
-
     }
 
     public static final @NonNull Creator<PendingIntent> CREATOR = new Creator<PendingIntent>() {
@@ -1483,9 +1505,10 @@
             @NonNull Parcel out) {
         out.writeStrongBinder(sender != null ? sender.mTarget.asBinder() : null);
         if (sender != null) {
-            OnMarshaledListener listener = sOnMarshaledListener.get();
-            if (listener != null) {
-                listener.onMarshaled(sender, out, 0 /* flags */);
+            final List<OnMarshaledListener> listeners = sOnMarshaledListener.get();
+            final int numListeners = listeners.size();
+            for (int i = 0; i < numListeners; i++) {
+                listeners.get(i).onMarshaled(sender, out, 0 /* flags */);
             }
         }
     }
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/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/service/wallpaper/WallpaperService.java b/core/java/android/service/wallpaper/WallpaperService.java
index 8eb02a1..dbc1be1 100644
--- a/core/java/android/service/wallpaper/WallpaperService.java
+++ b/core/java/android/service/wallpaper/WallpaperService.java
@@ -432,7 +432,8 @@
             @Override
             public void resized(ClientWindowFrames frames, boolean reportDraw,
                     MergedConfiguration mergedConfiguration, InsetsState insetsState,
-                    boolean forceLayout, int displayId, int syncSeqId, boolean dragResizing) {
+                    boolean forceLayout, boolean alwaysConsumeSystemBars, int displayId,
+                    int syncSeqId, boolean dragResizing) {
                 Message msg = mCaller.obtainMessageIO(MSG_WINDOW_RESIZED,
                         reportDraw ? 1 : 0,
                         mergedConfiguration);
@@ -1286,9 +1287,9 @@
                     visibleFrame.intersect(mInsetsState.getDisplayFrame());
                     WindowInsets windowInsets = mInsetsState.calculateInsets(visibleFrame,
                             null /* ignoringVisibilityState */, config.isScreenRound(),
-                            mLayout.softInputMode, mLayout.flags, SYSTEM_UI_FLAG_VISIBLE,
-                            mLayout.type, config.windowConfiguration.getWindowingMode(),
-                            null /* idSideMap */);
+                            false /* alwaysConsumeSystemBars */, mLayout.softInputMode,
+                            mLayout.flags, SYSTEM_UI_FLAG_VISIBLE, mLayout.type,
+                            config.windowConfiguration.getWindowingMode(), null /* idSideMap */);
 
                     if (!fixedSize) {
                         final Rect padding = mIWallpaperEngine.mDisplayPadding;
diff --git a/core/java/android/view/IWindow.aidl b/core/java/android/view/IWindow.aidl
index 33edc27..d554514 100644
--- a/core/java/android/view/IWindow.aidl
+++ b/core/java/android/view/IWindow.aidl
@@ -56,7 +56,8 @@
 
     void resized(in ClientWindowFrames frames, boolean reportDraw,
             in MergedConfiguration newMergedConfiguration, in InsetsState insetsState,
-            boolean forceLayout, int displayId, int syncSeqId, boolean dragResizing);
+            boolean forceLayout, boolean alwaysConsumeSystemBars, int displayId,
+            int syncSeqId, boolean dragResizing);
 
     /**
      * Called when this window retrieved control over a specified set of insets sources.
diff --git a/core/java/android/view/IWindowManager.aidl b/core/java/android/view/IWindowManager.aidl
index 8813c39..6d96bb94 100644
--- a/core/java/android/view/IWindowManager.aidl
+++ b/core/java/android/view/IWindowManager.aidl
@@ -737,8 +737,10 @@
 
     /**
      * Called to get the expected window insets.
+     *
+     * @return {@code true} if system bars are always consumed.
      */
-    void getWindowInsets(int displayId, in IBinder token, out InsetsState outInsetsState);
+    boolean getWindowInsets(int displayId, in IBinder token, out InsetsState outInsetsState);
 
     /**
      * Returns a list of {@link android.view.DisplayInfo} for the logical display. This is not
diff --git a/core/java/android/view/InsetsAnimationControlImpl.java b/core/java/android/view/InsetsAnimationControlImpl.java
index 4c50858..6c5f195 100644
--- a/core/java/android/view/InsetsAnimationControlImpl.java
+++ b/core/java/android/view/InsetsAnimationControlImpl.java
@@ -40,7 +40,6 @@
 import static android.view.InsetsState.ISIDE_RIGHT;
 import static android.view.InsetsState.ISIDE_TOP;
 import static android.view.WindowInsets.Type.ime;
-import static android.view.WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE;
 import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION;
 import static android.view.inputmethod.ImeTracker.DEBUG_IME_VISIBILITY;
 import static android.view.inputmethod.ImeTracker.TOKEN_NONE;
@@ -64,6 +63,7 @@
 import android.view.SyncRtSurfaceTransactionApplier.SurfaceParams;
 import android.view.WindowInsets.Type.InsetsType;
 import android.view.WindowInsetsAnimation.Bounds;
+import android.view.WindowManager.LayoutParams;
 import android.view.animation.Interpolator;
 import android.view.inputmethod.ImeTracker;
 
@@ -401,7 +401,8 @@
     private Insets getInsetsFromState(InsetsState state, Rect frame,
             @Nullable @InternalInsetsSide SparseIntArray idSideMap) {
         return state.calculateInsets(frame, null /* ignoringVisibilityState */,
-                false /* isScreenRound */, SOFT_INPUT_ADJUST_RESIZE /* legacySoftInputMode */,
+                false /* isScreenRound */, false /* alwaysConsumeSystemBars */,
+                LayoutParams.SOFT_INPUT_ADJUST_RESIZE /* legacySoftInputMode*/,
                 0 /* legacyWindowFlags */, 0 /* legacySystemUiFlags */, TYPE_APPLICATION,
                 WINDOWING_MODE_UNDEFINED, idSideMap).getInsets(mTypes);
     }
diff --git a/core/java/android/view/InsetsController.java b/core/java/android/view/InsetsController.java
index e5d031b..1d58268 100644
--- a/core/java/android/view/InsetsController.java
+++ b/core/java/android/view/InsetsController.java
@@ -302,11 +302,9 @@
     public static final int ANIMATION_TYPE_NONE = -1;
 
     /** Running animation will show insets */
-    @VisibleForTesting
     public static final int ANIMATION_TYPE_SHOW = 0;
 
     /** Running animation will hide insets */
-    @VisibleForTesting
     public static final int ANIMATION_TYPE_HIDE = 1;
 
     /** Running animation is controlled by user via {@link #controlWindowInsetsAnimation} */
@@ -447,21 +445,21 @@
                     if (mInputMethodJankContext == null) return;
                     ImeTracker.forJank().onRequestAnimation(
                             mInputMethodJankContext,
-                            mShow ? ANIMATION_TYPE_SHOW : ANIMATION_TYPE_HIDE,
+                            getAnimationType(),
                             !mHasAnimationCallbacks);
                 }
 
                 @Override
                 public void onAnimationCancel(Animator animation) {
                     if (mInputMethodJankContext == null) return;
-                    ImeTracker.forJank().onCancelAnimation();
+                    ImeTracker.forJank().onCancelAnimation(getAnimationType());
                 }
 
                 @Override
                 public void onAnimationEnd(Animator animation) {
                     onAnimationFinish();
                     if (mInputMethodJankContext == null) return;
-                    ImeTracker.forJank().onFinishAnimation();
+                    ImeTracker.forJank().onFinishAnimation(getAnimationType());
                 }
             });
             if (!mHasAnimationCallbacks) {
@@ -562,6 +560,14 @@
                 }
             }
         }
+
+        /**
+         * Returns the current animation type.
+         */
+        @AnimationType
+        private int getAnimationType() {
+            return mShow ? ANIMATION_TYPE_SHOW : ANIMATION_TYPE_HIDE;
+        }
     }
 
     /**
@@ -797,9 +803,9 @@
             }
 
             WindowInsets insets = state.calculateInsets(mFrame, mState /* ignoringVisibilityState*/,
-                    mLastInsets.isRound(), mLastLegacySoftInputMode, mLastLegacyWindowFlags,
-                    mLastLegacySystemUiFlags, mWindowType, mLastWindowingMode,
-                    null /* idSideMap */);
+                    mLastInsets.isRound(), false /* alwaysConsumeSystemBars */,
+                    mLastLegacySoftInputMode, mLastLegacyWindowFlags, mLastLegacySystemUiFlags,
+                    mWindowType, mLastWindowingMode, null /* idSideMap */);
             mHost.dispatchWindowInsetsAnimationProgress(insets,
                     Collections.unmodifiableList(runningAnimations));
             if (DEBUG) {
@@ -841,7 +847,6 @@
         return mLastDispatchedState;
     }
 
-    @VisibleForTesting
     public boolean onStateChanged(InsetsState state) {
         boolean stateChanged = false;
         if (!CAPTION_ON_SHELL) {
@@ -955,20 +960,21 @@
     }
 
     /**
-     * @see InsetsState#calculateInsets(Rect, InsetsState, boolean, int, int, int, int, int,
-     *      android.util.SparseIntArray)
+     * @see InsetsState#calculateInsets(Rect, InsetsState, boolean, boolean, int, int, int, int,
+     *      int, android.util.SparseIntArray)
      */
     @VisibleForTesting
-    public WindowInsets calculateInsets(boolean isScreenRound, int windowType, int windowingMode,
-            int legacySoftInputMode, int legacyWindowFlags, int legacySystemUiFlags) {
+    public WindowInsets calculateInsets(boolean isScreenRound, boolean alwaysConsumeSystemBars,
+            int windowType, int windowingMode, int legacySoftInputMode, int legacyWindowFlags,
+            int legacySystemUiFlags) {
         mWindowType = windowType;
         mLastWindowingMode = windowingMode;
         mLastLegacySoftInputMode = legacySoftInputMode;
         mLastLegacyWindowFlags = legacyWindowFlags;
         mLastLegacySystemUiFlags = legacySystemUiFlags;
         mLastInsets = mState.calculateInsets(mFrame, null /* ignoringVisibilityState*/,
-                isScreenRound, legacySoftInputMode, legacyWindowFlags, legacySystemUiFlags,
-                windowType, windowingMode, null /* idSideMap */);
+                isScreenRound, alwaysConsumeSystemBars, legacySoftInputMode, legacyWindowFlags,
+                legacySystemUiFlags, windowType, windowingMode, null /* idSideMap */);
         return mLastInsets;
     }
 
diff --git a/core/java/android/view/InsetsState.java b/core/java/android/view/InsetsState.java
index 828938b..c13b9ab 100644
--- a/core/java/android/view/InsetsState.java
+++ b/core/java/android/view/InsetsState.java
@@ -40,7 +40,6 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.app.WindowConfiguration;
-import android.app.WindowConfiguration.WindowingMode;
 import android.graphics.Insets;
 import android.graphics.Rect;
 import android.os.Parcel;
@@ -137,8 +136,9 @@
      * @return The calculated insets.
      */
     public WindowInsets calculateInsets(Rect frame, @Nullable InsetsState ignoringVisibilityState,
-            boolean isScreenRound, int legacySoftInputMode, int legacyWindowFlags,
-            int legacySystemUiFlags, int windowType, @WindowingMode int windowingMode,
+            boolean isScreenRound, boolean alwaysConsumeSystemBars,
+            int legacySoftInputMode, int legacyWindowFlags, int legacySystemUiFlags,
+            int windowType, @WindowConfiguration.WindowingMode int windowingMode,
             @Nullable @InternalInsetsSide SparseIntArray idSideMap) {
         Insets[] typeInsetsMap = new Insets[Type.SIZE];
         Insets[] typeMaxInsetsMap = new Insets[Type.SIZE];
diff --git a/core/java/android/view/SurfaceView.java b/core/java/android/view/SurfaceView.java
index 1e268be..5b69d7f 100644
--- a/core/java/android/view/SurfaceView.java
+++ b/core/java/android/view/SurfaceView.java
@@ -1340,7 +1340,7 @@
             mSurfaceControl = new SurfaceControl.Builder(mSurfaceSession)
                     .setName(name)
                     .setLocalOwnerView(this)
-                    .setParent(viewRoot.getBoundsLayer())
+                    .setParent(viewRoot.updateAndGetBoundsLayer(surfaceUpdateTransaction))
                     .setCallsite("SurfaceView.updateSurface")
                     .setContainerLayer()
                     .build();
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index f51e3a3..f1cde3b 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -30744,6 +30744,13 @@
         final Rect mCaptionInsets = new Rect();
 
         /**
+         * In multi-window we force show the system bars. Because we don't want that the surface
+         * size changes in this mode, we instead have a flag whether the system bars sizes should
+         * always be consumed, so the app is treated like there are no virtual system bars at all.
+         */
+        boolean mAlwaysConsumeSystemBars;
+
+        /**
          * The internal insets given by this window.  This value is
          * supplied by the client (through
          * {@link ViewTreeObserver.OnComputeInternalInsetsListener}) and will
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index ef76f4a..9d2bb58 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -82,6 +82,7 @@
 import static android.view.WindowManager.LayoutParams.TYPE_VOLUME_OVERLAY;
 import static android.view.WindowManager.PROPERTY_COMPAT_ALLOW_SANDBOXING_VIEW_BOUNDS_APIS;
 import static android.view.WindowManagerGlobal.RELAYOUT_RES_CANCEL_AND_REDRAW;
+import static android.view.WindowManagerGlobal.RELAYOUT_RES_CONSUME_ALWAYS_SYSTEM_BARS;
 import static android.view.WindowManagerGlobal.RELAYOUT_RES_SURFACE_CHANGED;
 import static android.view.inputmethod.InputMethodEditorTraceProto.InputMethodClientsTraceProto.ClientSideProto.IME_FOCUS_CONTROLLER;
 import static android.view.inputmethod.InputMethodEditorTraceProto.InputMethodClientsTraceProto.ClientSideProto.INSETS_CONTROLLER;
@@ -715,9 +716,9 @@
 
     /**
      * Child container layer of {@code mSurface} with the same bounds as its parent, and cropped to
-     * the surface insets. This surface is created only if a client requests it via {@link
-     * #getBoundsLayer()}. By parenting to this bounds surface, child surfaces can ensure they do
-     * not draw into the surface inset region set by the parent window.
+     * the surface insets. This surface is created only if a client requests it via
+     * {@link #updateAndGetBoundsLayer(Transaction)}. By parenting to this bounds surface, child
+     * surfaces can ensure they do not draw into the surface inset region set by the parent window.
      */
     private SurfaceControl mBoundsLayer;
     private final SurfaceSession mSurfaceSession = new SurfaceSession();
@@ -740,6 +741,7 @@
 
     final Rect mPendingBackDropFrame = new Rect();
 
+    boolean mPendingAlwaysConsumeSystemBars;
     private int mRelayoutSeq;
     private final Rect mWinFrameInScreen = new Rect();
     private final InsetsState mTempInsets = new InsetsState();
@@ -1364,6 +1366,9 @@
                     }
                 }
 
+                mAttachInfo.mAlwaysConsumeSystemBars =
+                        (res & WindowManagerGlobal.ADD_FLAG_ALWAYS_CONSUME_SYSTEM_BARS) != 0;
+                mPendingAlwaysConsumeSystemBars = mAttachInfo.mAlwaysConsumeSystemBars;
                 mInsetsController.onStateChanged(mTempInsets);
                 mInsetsController.onControlsChanged(mTempControls.get());
                 final InsetsState state = mInsetsController.getState();
@@ -1877,8 +1882,8 @@
         final MergedConfiguration mergedConfiguration = (MergedConfiguration) args.arg2;
         CompatibilityInfo.applyOverrideScaleIfNeeded(mergedConfiguration);
         final boolean forceNextWindowRelayout = args.argi1 != 0;
-        final int displayId = args.argi2;
-        final boolean dragResizing = args.argi4 != 0;
+        final int displayId = args.argi3;
+        final boolean dragResizing = args.argi5 != 0;
 
         final Rect frame = frames.frame;
         final Rect displayFrame = frames.displayFrame;
@@ -1930,7 +1935,8 @@
         }
 
         mForceNextWindowRelayout |= forceNextWindowRelayout;
-        mSyncSeqId = args.argi3 > mSyncSeqId ? args.argi3 : mSyncSeqId;
+        mPendingAlwaysConsumeSystemBars = args.argi2 != 0;
+        mSyncSeqId = args.argi4 > mSyncSeqId ? args.argi4 : mSyncSeqId;
 
         if (msg == MSG_RESIZED_REPORT) {
             reportNextDraw("resized");
@@ -2256,7 +2262,7 @@
      * <p>Parenting to this layer will ensure that its children are cropped by the view's surface
      * insets.
      */
-    public SurfaceControl getBoundsLayer() {
+    public SurfaceControl updateAndGetBoundsLayer(Transaction t) {
         if (mBoundsLayer == null) {
             mBoundsLayer = new SurfaceControl.Builder(mSurfaceSession)
                     .setContainerLayer()
@@ -2264,8 +2270,8 @@
                     .setParent(getSurfaceControl())
                     .setCallsite("ViewRootImpl.getBoundsLayer")
                     .build();
-            setBoundsLayerCrop(mTransaction);
-            mTransaction.show(mBoundsLayer).apply();
+            setBoundsLayerCrop(t);
+            t.show(mBoundsLayer);
         }
        return mBoundsLayer;
     }
@@ -2815,8 +2821,8 @@
         if (mLastWindowInsets == null || forceConstruct) {
             final Configuration config = getConfiguration();
             mLastWindowInsets = mInsetsController.calculateInsets(
-                    config.isScreenRound(), mWindowAttributes.type,
-                    config.windowConfiguration.getWindowingMode(),
+                    config.isScreenRound(), mAttachInfo.mAlwaysConsumeSystemBars,
+                    mWindowAttributes.type, config.windowConfiguration.getWindowingMode(),
                     mWindowAttributes.softInputMode, mWindowAttributes.flags,
                     (mWindowAttributes.systemUiVisibility
                             | mWindowAttributes.subtreeSystemUiVisibility));
@@ -3278,6 +3284,8 @@
                     surfaceSizeChanged = true;
                     mLastSurfaceSize.set(mSurfaceSize.x, mSurfaceSize.y);
                 }
+                final boolean alwaysConsumeSystemBarsChanged =
+                        mPendingAlwaysConsumeSystemBars != mAttachInfo.mAlwaysConsumeSystemBars;
                 updateColorModeIfNeeded(lp.getColorMode());
                 surfaceCreated = !hadSurface && mSurface.isValid();
                 surfaceDestroyed = hadSurface && !mSurface.isValid();
@@ -3291,6 +3299,10 @@
                 if (surfaceReplaced) {
                     mSurfaceSequenceId++;
                 }
+                if (alwaysConsumeSystemBarsChanged) {
+                    mAttachInfo.mAlwaysConsumeSystemBars = mPendingAlwaysConsumeSystemBars;
+                    dispatchApplyInsets = true;
+                }
                 if (updateCaptionInsets()) {
                     dispatchApplyInsets = true;
                 }
@@ -8289,6 +8301,9 @@
             CompatibilityInfo.applyOverrideScaleIfNeeded(mPendingMergedConfiguration);
             mInsetsController.onStateChanged(mTempInsets);
             mInsetsController.onControlsChanged(mTempControls.get());
+
+            mPendingAlwaysConsumeSystemBars =
+                    (relayoutResult & RELAYOUT_RES_CONSUME_ALWAYS_SYSTEM_BARS) != 0;
         }
 
         final int transformHint = SurfaceControl.rotationToBufferTransform(
@@ -8911,7 +8926,7 @@
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
     private void dispatchResized(ClientWindowFrames frames, boolean reportDraw,
             MergedConfiguration mergedConfiguration, InsetsState insetsState, boolean forceLayout,
-            int displayId, int syncSeqId, boolean dragResizing) {
+            boolean alwaysConsumeSystemBars, int displayId, int syncSeqId, boolean dragResizing) {
         Message msg = mHandler.obtainMessage(reportDraw ? MSG_RESIZED_REPORT : MSG_RESIZED);
         SomeArgs args = SomeArgs.obtain();
         final boolean sameProcessCall = (Binder.getCallingPid() == android.os.Process.myPid());
@@ -8930,9 +8945,10 @@
                 ? new MergedConfiguration(mergedConfiguration) : mergedConfiguration;
         args.arg3 = insetsState;
         args.argi1 = forceLayout ? 1 : 0;
-        args.argi2 = displayId;
-        args.argi3 = syncSeqId;
-        args.argi4 = dragResizing ? 1 : 0;
+        args.argi2 = alwaysConsumeSystemBars ? 1 : 0;
+        args.argi3 = displayId;
+        args.argi4 = syncSeqId;
+        args.argi5 = dragResizing ? 1 : 0;
 
         msg.obj = args;
         mHandler.sendMessage(msg);
@@ -10326,11 +10342,12 @@
         @Override
         public void resized(ClientWindowFrames frames, boolean reportDraw,
                 MergedConfiguration mergedConfiguration, InsetsState insetsState,
-                boolean forceLayout, int displayId, int syncSeqId, boolean dragResizing) {
+                boolean forceLayout, boolean alwaysConsumeSystemBars, int displayId, int syncSeqId,
+                boolean dragResizing) {
             final ViewRootImpl viewAncestor = mViewAncestor.get();
             if (viewAncestor != null) {
                 viewAncestor.dispatchResized(frames, reportDraw, mergedConfiguration, insetsState,
-                        forceLayout, displayId, syncSeqId, dragResizing);
+                        forceLayout, alwaysConsumeSystemBars, displayId, syncSeqId, dragResizing);
             }
         }
 
@@ -11171,7 +11188,8 @@
     @Nullable public SurfaceControl.Transaction buildReparentTransaction(
         @NonNull SurfaceControl child) {
         if (mSurfaceControl.isValid()) {
-          return new SurfaceControl.Transaction().reparent(child, getBoundsLayer());
+            Transaction t = new Transaction();
+            return t.reparent(child, updateAndGetBoundsLayer(t));
         }
         return null;
     }
diff --git a/core/java/android/view/WindowInsets.java b/core/java/android/view/WindowInsets.java
index 7757c25..57a4161 100644
--- a/core/java/android/view/WindowInsets.java
+++ b/core/java/android/view/WindowInsets.java
@@ -1429,6 +1429,14 @@
 
         /** @hide */
         @NonNull
+        public Builder setAlwaysConsumeSystemBars(boolean 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;
diff --git a/core/java/android/view/WindowManagerGlobal.java b/core/java/android/view/WindowManagerGlobal.java
index c9368f1..99a4f6b 100644
--- a/core/java/android/view/WindowManagerGlobal.java
+++ b/core/java/android/view/WindowManagerGlobal.java
@@ -79,9 +79,16 @@
     public static final int RELAYOUT_RES_SURFACE_RESIZED = 1 << 2;
 
     /**
+     * In multi-window we force show the system bars. Because we don't want that the surface size
+     * changes in this mode, we instead have a flag whether the system bar sizes should always be
+     * consumed, so the app is treated like there is no virtual system bars at all.
+     */
+    public static final int RELAYOUT_RES_CONSUME_ALWAYS_SYSTEM_BARS = 1 << 3;
+
+    /**
      * The window manager has told the window it cannot draw this frame and should retry again.
      */
-    public static final int RELAYOUT_RES_CANCEL_AND_REDRAW = 1 << 3;
+    public static final int RELAYOUT_RES_CANCEL_AND_REDRAW = 1 << 4;
 
     /**
      * Flag for relayout: the client will be later giving
@@ -92,7 +99,13 @@
 
     public static final int ADD_FLAG_IN_TOUCH_MODE = 0x1;
     public static final int ADD_FLAG_APP_VISIBLE = 0x2;
-    public static final int ADD_FLAG_USE_BLAST = 0x4;
+    public static final int ADD_FLAG_USE_BLAST = 0x8;
+
+    /**
+     * Like {@link #RELAYOUT_RES_CONSUME_ALWAYS_SYSTEM_BARS}, but as a "hint" when adding the
+     * window.
+     */
+    public static final int ADD_FLAG_ALWAYS_CONSUME_SYSTEM_BARS = 0x4;
 
     public static final int ADD_OKAY = 0;
     public static final int ADD_BAD_APP_TOKEN = -1;
diff --git a/core/java/android/view/WindowlessWindowManager.java b/core/java/android/view/WindowlessWindowManager.java
index 1efac4d..7d3d283 100644
--- a/core/java/android/view/WindowlessWindowManager.java
+++ b/core/java/android/view/WindowlessWindowManager.java
@@ -631,8 +631,8 @@
                 mTmpFrames.displayFrame.set(mTmpFrames.frame);
                 mTmpConfig.setConfiguration(mConfiguration, mConfiguration);
                 s.mClient.resized(mTmpFrames, false /* reportDraw */, mTmpConfig, state,
-                        false /* forceLayout */, s.mDisplayId, Integer.MAX_VALUE,
-                        false /* dragResizing */);
+                        false /* forceLayout */, false /* alwaysConsumeSystemBars */, s.mDisplayId,
+                        Integer.MAX_VALUE, false /* dragResizing */);
             } catch (RemoteException e) {
                 // Too bad
             }
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/ImeTracker.java b/core/java/android/view/inputmethod/ImeTracker.java
index f0d1019..03d1cd8 100644
--- a/core/java/android/view/inputmethod/ImeTracker.java
+++ b/core/java/android/view/inputmethod/ImeTracker.java
@@ -16,8 +16,12 @@
 
 package android.view.inputmethod;
 
+import static android.view.InsetsController.ANIMATION_TYPE_HIDE;
+import static android.view.InsetsController.ANIMATION_TYPE_SHOW;
+
 import static com.android.internal.inputmethod.InputMethodDebug.softInputDisplayReasonToString;
-import static com.android.internal.jank.InteractionJankMonitor.CUJ_IME_INSETS_ANIMATION;
+import static com.android.internal.jank.InteractionJankMonitor.CUJ_IME_INSETS_HIDE_ANIMATION;
+import static com.android.internal.jank.InteractionJankMonitor.CUJ_IME_INSETS_SHOW_ANIMATION;
 import static com.android.internal.util.LatencyTracker.ACTION_REQUEST_IME_HIDDEN;
 import static com.android.internal.util.LatencyTracker.ACTION_REQUEST_IME_SHOWN;
 
@@ -696,20 +700,22 @@
         /**
          * Called when the animation, which is going to be monitored, starts.
          *
-         * @param jankContext context which is needed by {@link InteractionJankMonitor}
-         * @param animType {@link AnimationType}
+         * @param jankContext context which is needed by {@link InteractionJankMonitor}.
+         * @param animType the animation type.
          * @param useSeparatedThread {@code true} if the animation is handled by the app,
          *                           {@code false} if the animation will be scheduled on the
-         *                           {@link android.view.InsetsAnimationThread}
+         *                           {@link android.view.InsetsAnimationThread}.
          */
         public void onRequestAnimation(@NonNull InputMethodJankContext jankContext,
                 @AnimationType int animType, boolean useSeparatedThread) {
+            final int cujType = getImeInsetsCujFromAnimation(animType);
             if (jankContext.getDisplayContext() == null
-                    || jankContext.getTargetSurfaceControl() == null) {
+                    || jankContext.getTargetSurfaceControl() == null
+                    || cujType == -1) {
                 return;
             }
             final Configuration.Builder builder = Configuration.Builder.withSurface(
-                            CUJ_IME_INSETS_ANIMATION,
+                            cujType,
                             jankContext.getDisplayContext(),
                             jankContext.getTargetSurfaceControl())
                     .setTag(String.format(Locale.US, "%d@%d@%s", animType,
@@ -719,16 +725,44 @@
 
         /**
          * Called when the animation, which is going to be monitored, cancels.
+         *
+         * @param animType the animation type.
          */
-        public void onCancelAnimation() {
-            InteractionJankMonitor.getInstance().cancel(CUJ_IME_INSETS_ANIMATION);
+        public void onCancelAnimation(@AnimationType int animType) {
+            final int cujType = getImeInsetsCujFromAnimation(animType);
+            if (cujType == -1) {
+                InteractionJankMonitor.getInstance().cancel(cujType);
+            }
         }
 
         /**
          * Called when the animation, which is going to be monitored, ends.
+         *
+         * @param animType the animation type.
          */
-        public void onFinishAnimation() {
-            InteractionJankMonitor.getInstance().end(CUJ_IME_INSETS_ANIMATION);
+        public void onFinishAnimation(@AnimationType int animType) {
+            final int cujType = getImeInsetsCujFromAnimation(animType);
+            if (cujType != -1) {
+                InteractionJankMonitor.getInstance().end(cujType);
+            }
+        }
+
+        /**
+         * A helper method to translate animation type to CUJ type for IME animations.
+         *
+         * @param animType the animation type.
+         * @return the integer in {@link com.android.internal.jank.InteractionJankMonitor.CujType},
+         * or {@code -1} if the animation type is not supported for tracking yet.
+         */
+        private static int getImeInsetsCujFromAnimation(@AnimationType int animType) {
+            switch (animType) {
+                case ANIMATION_TYPE_SHOW:
+                    return CUJ_IME_INSETS_SHOW_ANIMATION;
+                case ANIMATION_TYPE_HIDE:
+                    return CUJ_IME_INSETS_HIDE_ANIMATION;
+                default:
+                    return -1;
+            }
         }
     }
 
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/window/WindowContainerTransaction.java b/core/java/android/window/WindowContainerTransaction.java
index 9f804b1..3323ae5 100644
--- a/core/java/android/window/WindowContainerTransaction.java
+++ b/core/java/android/window/WindowContainerTransaction.java
@@ -688,6 +688,7 @@
                         .setInsetsFrameProvider(new InsetsFrameProvider(owner, index, type)
                                 .setSource(InsetsFrameProvider.SOURCE_ARBITRARY_RECTANGLE)
                                 .setArbitraryRectangle(frame))
+                        .setInsetsFrameOwner(owner)
                         .build();
         mHierarchyOps.add(hierarchyOp);
         return this;
@@ -712,6 +713,7 @@
                 new HierarchyOp.Builder(HierarchyOp.HIERARCHY_OP_TYPE_REMOVE_INSETS_FRAME_PROVIDER)
                         .setContainer(receiver.asBinder())
                         .setInsetsFrameProvider(new InsetsFrameProvider(owner, index, type))
+                        .setInsetsFrameOwner(owner)
                         .build();
         mHierarchyOps.add(hierarchyOp);
         return this;
@@ -1344,8 +1346,12 @@
         @Nullable
         private IBinder mReparent;
 
+        @Nullable
         private InsetsFrameProvider mInsetsFrameProvider;
 
+        @Nullable
+        private IBinder mInsetsFrameOwner;
+
         // Moves/reparents to top of parent when {@code true}, otherwise moves/reparents to bottom.
         private boolean mToTop;
 
@@ -1478,6 +1484,7 @@
             mContainer = copy.mContainer;
             mReparent = copy.mReparent;
             mInsetsFrameProvider = copy.mInsetsFrameProvider;
+            mInsetsFrameOwner = copy.mInsetsFrameOwner;
             mToTop = copy.mToTop;
             mReparentTopOnly = copy.mReparentTopOnly;
             mWindowingModes = copy.mWindowingModes;
@@ -1496,6 +1503,7 @@
             mContainer = in.readStrongBinder();
             mReparent = in.readStrongBinder();
             mInsetsFrameProvider = in.readTypedObject(InsetsFrameProvider.CREATOR);
+            mInsetsFrameOwner = in.readStrongBinder();
             mToTop = in.readBoolean();
             mReparentTopOnly = in.readBoolean();
             mWindowingModes = in.createIntArray();
@@ -1527,6 +1535,11 @@
             return mInsetsFrameProvider;
         }
 
+        @Nullable
+        public IBinder getInsetsFrameOwner() {
+            return mInsetsFrameOwner;
+        }
+
         @NonNull
         public IBinder getContainer() {
             return mContainer;
@@ -1657,7 +1670,8 @@
                 case HIERARCHY_OP_TYPE_ADD_INSETS_FRAME_PROVIDER:
                 case HIERARCHY_OP_TYPE_REMOVE_INSETS_FRAME_PROVIDER:
                     sb.append("container=").append(mContainer)
-                            .append(" provider=").append(mInsetsFrameProvider);
+                            .append(" provider=").append(mInsetsFrameProvider)
+                            .append(" owner=").append(mInsetsFrameOwner);
                     break;
                 case HIERARCHY_OP_TYPE_SET_ALWAYS_ON_TOP:
                     sb.append("container=").append(mContainer)
@@ -1697,6 +1711,7 @@
             dest.writeStrongBinder(mContainer);
             dest.writeStrongBinder(mReparent);
             dest.writeTypedObject(mInsetsFrameProvider, flags);
+            dest.writeStrongBinder(mInsetsFrameOwner);
             dest.writeBoolean(mToTop);
             dest.writeBoolean(mReparentTopOnly);
             dest.writeIntArray(mWindowingModes);
@@ -1737,8 +1752,12 @@
             @Nullable
             private IBinder mReparent;
 
+            @Nullable
             private InsetsFrameProvider mInsetsFrameProvider;
 
+            @Nullable
+            private IBinder mInsetsFrameOwner;
+
             private boolean mToTop;
 
             private boolean mReparentTopOnly;
@@ -1782,8 +1801,13 @@
                 return this;
             }
 
-            Builder setInsetsFrameProvider(InsetsFrameProvider providers) {
-                mInsetsFrameProvider = providers;
+            Builder setInsetsFrameProvider(InsetsFrameProvider provider) {
+                mInsetsFrameProvider = provider;
+                return this;
+            }
+
+            Builder setInsetsFrameOwner(IBinder owner) {
+                mInsetsFrameOwner = owner;
                 return this;
             }
 
@@ -1854,6 +1878,7 @@
                         ? Arrays.copyOf(mActivityTypes, mActivityTypes.length)
                         : null;
                 hierarchyOp.mInsetsFrameProvider = mInsetsFrameProvider;
+                hierarchyOp.mInsetsFrameOwner = mInsetsFrameOwner;
                 hierarchyOp.mToTop = mToTop;
                 hierarchyOp.mReparentTopOnly = mReparentTopOnly;
                 hierarchyOp.mLaunchOptions = mLaunchOptions;
diff --git a/core/java/android/window/WindowMetricsController.java b/core/java/android/window/WindowMetricsController.java
index d08d5cf..2858f0a 100644
--- a/core/java/android/window/WindowMetricsController.java
+++ b/core/java/android/window/WindowMetricsController.java
@@ -112,14 +112,15 @@
             Rect bounds, boolean isScreenRound, int windowingMode) {
         try {
             final InsetsState insetsState = new InsetsState();
-            WindowManagerGlobal.getWindowManagerService().getWindowInsets(
-                    displayId, token, insetsState);
+            final boolean alwaysConsumeSystemBars = WindowManagerGlobal.getWindowManagerService()
+                    .getWindowInsets(displayId, token, insetsState);
             final float overrideInvScale = CompatibilityInfo.getOverrideInvertedScale();
             if (overrideInvScale != 1f) {
                 insetsState.scale(overrideInvScale);
             }
             return insetsState.calculateInsets(bounds, null /* ignoringVisibilityState */,
-                    isScreenRound, SOFT_INPUT_ADJUST_NOTHING, 0 /* flags */, SYSTEM_UI_FLAG_VISIBLE,
+                    isScreenRound, alwaysConsumeSystemBars, SOFT_INPUT_ADJUST_NOTHING,
+                    0 /* flags */, SYSTEM_UI_FLAG_VISIBLE,
                     WindowManager.LayoutParams.INVALID_WINDOW_TYPE, windowingMode,
                     null /* idSideMap */);
         } catch (RemoteException e) {
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/jank/InteractionJankMonitor.java b/core/java/com/android/internal/jank/InteractionJankMonitor.java
index 92427ec..03d7450 100644
--- a/core/java/com/android/internal/jank/InteractionJankMonitor.java
+++ b/core/java/com/android/internal/jank/InteractionJankMonitor.java
@@ -25,7 +25,8 @@
 import static com.android.internal.jank.FrameTracker.REASON_END_NORMAL;
 import static com.android.internal.jank.FrameTracker.REASON_END_UNKNOWN;
 import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__BIOMETRIC_PROMPT_TRANSITION;
-import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__IME_INSETS_ANIMATION;
+import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__IME_INSETS_HIDE_ANIMATION;
+import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__IME_INSETS_SHOW_ANIMATION;
 import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_ALL_APPS_SCROLL;
 import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_APP_CLOSE_TO_HOME;
 import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_APP_CLOSE_TO_HOME_FALLBACK;
@@ -257,7 +258,6 @@
     public static final int CUJ_LAUNCHER_APP_SWIPE_TO_RECENTS = 66;
     public static final int CUJ_LAUNCHER_CLOSE_ALL_APPS_SWIPE = 67;
     public static final int CUJ_LAUNCHER_CLOSE_ALL_APPS_TO_HOME = 68;
-    public static final int CUJ_IME_INSETS_ANIMATION = 69;
     public static final int CUJ_LOCKSCREEN_CLOCK_MOVE_ANIMATION = 70;
     public static final int CUJ_LAUNCHER_OPEN_SEARCH_RESULT = 71;
     // 72 - 77 are reserved for b/281564325.
@@ -269,8 +269,10 @@
      */
     public static final int CUJ_LAUNCHER_APP_CLOSE_TO_HOME_FALLBACK = 78;
     public static final int CUJ_SHADE_EXPAND_FROM_STATUS_BAR = 79;
+    public static final int CUJ_IME_INSETS_SHOW_ANIMATION = 80;
+    public static final int CUJ_IME_INSETS_HIDE_ANIMATION = 81;
 
-    private static final int LAST_CUJ = CUJ_SHADE_EXPAND_FROM_STATUS_BAR;
+    private static final int LAST_CUJ = CUJ_IME_INSETS_HIDE_ANIMATION;
     private static final int NO_STATSD_LOGGING = -1;
 
     // Used to convert CujType to InteractionType enum value for statsd logging.
@@ -348,7 +350,7 @@
         CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LAUNCHER_APP_SWIPE_TO_RECENTS] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_APP_SWIPE_TO_RECENTS;
         CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LAUNCHER_CLOSE_ALL_APPS_SWIPE] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_CLOSE_ALL_APPS_SWIPE;
         CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LAUNCHER_CLOSE_ALL_APPS_TO_HOME] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_CLOSE_ALL_APPS_TO_HOME;
-        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_IME_INSETS_ANIMATION] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__IME_INSETS_ANIMATION;
+        CUJ_TO_STATSD_INTERACTION_TYPE[69] = NO_STATSD_LOGGING; // This is deprecated.
         CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LOCKSCREEN_CLOCK_MOVE_ANIMATION] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LOCKSCREEN_CLOCK_MOVE_ANIMATION;
         CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LAUNCHER_OPEN_SEARCH_RESULT] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_OPEN_SEARCH_RESULT;
         // 72 - 77 are reserved for b/281564325.
@@ -360,6 +362,8 @@
         CUJ_TO_STATSD_INTERACTION_TYPE[77] = NO_STATSD_LOGGING;
         CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LAUNCHER_APP_CLOSE_TO_HOME_FALLBACK] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_APP_CLOSE_TO_HOME_FALLBACK;
         CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_SHADE_EXPAND_FROM_STATUS_BAR] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_EXPAND_FROM_STATUS_BAR;
+        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_IME_INSETS_SHOW_ANIMATION] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__IME_INSETS_SHOW_ANIMATION;
+        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_IME_INSETS_HIDE_ANIMATION] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__IME_INSETS_HIDE_ANIMATION;
     }
 
     private static class InstanceHolder {
@@ -456,11 +460,12 @@
             CUJ_LAUNCHER_APP_SWIPE_TO_RECENTS,
             CUJ_LAUNCHER_CLOSE_ALL_APPS_SWIPE,
             CUJ_LAUNCHER_CLOSE_ALL_APPS_TO_HOME,
-            CUJ_IME_INSETS_ANIMATION,
             CUJ_LOCKSCREEN_CLOCK_MOVE_ANIMATION,
             CUJ_LAUNCHER_OPEN_SEARCH_RESULT,
             CUJ_LAUNCHER_APP_CLOSE_TO_HOME_FALLBACK,
             CUJ_SHADE_EXPAND_FROM_STATUS_BAR,
+            CUJ_IME_INSETS_SHOW_ANIMATION,
+            CUJ_IME_INSETS_HIDE_ANIMATION,
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface CujType {
@@ -1066,8 +1071,6 @@
                 return "LAUNCHER_CLOSE_ALL_APPS_SWIPE";
             case CUJ_LAUNCHER_CLOSE_ALL_APPS_TO_HOME:
                 return "LAUNCHER_CLOSE_ALL_APPS_TO_HOME";
-            case CUJ_IME_INSETS_ANIMATION:
-                return "IME_INSETS_ANIMATION";
             case CUJ_LOCKSCREEN_CLOCK_MOVE_ANIMATION:
                 return "LOCKSCREEN_CLOCK_MOVE_ANIMATION";
             case CUJ_LAUNCHER_OPEN_SEARCH_RESULT:
@@ -1076,6 +1079,10 @@
                 return "LAUNCHER_APP_CLOSE_TO_HOME_FALLBACK";
             case CUJ_SHADE_EXPAND_FROM_STATUS_BAR:
                 return "SHADE_EXPAND_FROM_STATUS_BAR";
+            case CUJ_IME_INSETS_SHOW_ANIMATION:
+                return "IME_INSETS_SHOW_ANIMATION";
+            case CUJ_IME_INSETS_HIDE_ANIMATION:
+                return "IME_INSETS_HIDE_ANIMATION";
         }
         return "UNKNOWN";
     }
diff --git a/core/java/com/android/internal/view/BaseIWindow.java b/core/java/com/android/internal/view/BaseIWindow.java
index 0ac7765..600058e 100644
--- a/core/java/com/android/internal/view/BaseIWindow.java
+++ b/core/java/com/android/internal/view/BaseIWindow.java
@@ -53,7 +53,7 @@
     @Override
     public void resized(ClientWindowFrames frames, boolean reportDraw,
             MergedConfiguration mergedConfiguration, InsetsState insetsState, boolean forceLayout,
-            int displayId, int seqId, boolean dragResizing) {
+            boolean alwaysConsumeSystemBars, int displayId, int seqId, boolean dragResizing) {
         if (reportDraw) {
             try {
                 mSession.finishDrawing(this, null /* postDrawTransaction */, seqId);
diff --git a/core/tests/coretests/src/android/app/NotificationTest.java b/core/tests/coretests/src/android/app/NotificationTest.java
index eba7f58..7c69e65 100644
--- a/core/tests/coretests/src/android/app/NotificationTest.java
+++ b/core/tests/coretests/src/android/app/NotificationTest.java
@@ -271,6 +271,54 @@
     }
 
     @Test
+    public void allPendingIntents_resilientToAnotherNotificationInExtras() {
+        PendingIntent contentIntent = createPendingIntent("content");
+        PendingIntent actionIntent = createPendingIntent("action");
+        Notification another = new Notification.Builder(mContext, "channel").build();
+        Bundle bundleContainingAnotherNotification = new Bundle();
+        bundleContainingAnotherNotification.putParcelable(null, another);
+        Notification source = new Notification.Builder(mContext, "channel")
+                .setContentIntent(contentIntent)
+                .addAction(new Notification.Action.Builder(null, "action", actionIntent).build())
+                .setExtras(bundleContainingAnotherNotification)
+                .build();
+
+        Parcel p = Parcel.obtain();
+        source.writeToParcel(p, 0);
+        p.setDataPosition(0);
+        Notification unparceled = new Notification(p);
+
+        assertThat(unparceled.allPendingIntents).containsExactly(contentIntent, actionIntent);
+    }
+
+    @Test
+    public void allPendingIntents_alsoInPublicVersion() {
+        PendingIntent contentIntent = createPendingIntent("content");
+        PendingIntent actionIntent = createPendingIntent("action");
+        PendingIntent publicContentIntent = createPendingIntent("publicContent");
+        PendingIntent publicActionIntent = createPendingIntent("publicAction");
+        Notification source = new Notification.Builder(mContext, "channel")
+                .setContentIntent(contentIntent)
+                .addAction(new Notification.Action.Builder(null, "action", actionIntent).build())
+                .setPublicVersion(new Notification.Builder(mContext, "channel")
+                        .setContentIntent(publicContentIntent)
+                        .addAction(new Notification.Action.Builder(
+                                null, "publicAction", publicActionIntent).build())
+                        .build())
+                .build();
+
+        Parcel p = Parcel.obtain();
+        source.writeToParcel(p, 0);
+        p.setDataPosition(0);
+        Notification unparceled = new Notification(p);
+
+        assertThat(unparceled.allPendingIntents).containsExactly(contentIntent, actionIntent,
+                publicContentIntent, publicActionIntent);
+        assertThat(unparceled.publicVersion.allPendingIntents).containsExactly(publicContentIntent,
+                publicActionIntent);
+    }
+
+    @Test
     public void messagingStyle_isGroupConversation() {
         mContext.getApplicationInfo().targetSdkVersion = Build.VERSION_CODES.P;
         Notification.MessagingStyle messagingStyle = new Notification.MessagingStyle("self name")
diff --git a/core/tests/coretests/src/android/view/ImeInsetsSourceConsumerTest.java b/core/tests/coretests/src/android/view/ImeInsetsSourceConsumerTest.java
index a9c0e41..f45db23 100644
--- a/core/tests/coretests/src/android/view/ImeInsetsSourceConsumerTest.java
+++ b/core/tests/coretests/src/android/view/ImeInsetsSourceConsumerTest.java
@@ -81,6 +81,7 @@
                     Insets.of(10, 10, 10, 10), rect, rect, rect, rect));
             mController.calculateInsets(
                     false,
+                    false,
                     TYPE_APPLICATION, WINDOWING_MODE_UNDEFINED,
                     SOFT_INPUT_ADJUST_RESIZE, 0, 0);
             mImeConsumer = mController.getImeSourceConsumer();
diff --git a/core/tests/coretests/src/android/view/InsetsControllerTest.java b/core/tests/coretests/src/android/view/InsetsControllerTest.java
index 76e4a6b..0692052 100644
--- a/core/tests/coretests/src/android/view/InsetsControllerTest.java
+++ b/core/tests/coretests/src/android/view/InsetsControllerTest.java
@@ -171,6 +171,7 @@
             mController.onStateChanged(state);
             mController.calculateInsets(
                     false,
+                    false,
                     TYPE_APPLICATION, WINDOWING_MODE_UNDEFINED,
                     SOFT_INPUT_ADJUST_RESIZE, 0, 0);
             mController.onFrameChanged(new Rect(0, 0, 100, 100));
diff --git a/core/tests/coretests/src/android/view/InsetsStateTest.java b/core/tests/coretests/src/android/view/InsetsStateTest.java
index 6b32350..fde1a6d 100644
--- a/core/tests/coretests/src/android/view/InsetsStateTest.java
+++ b/core/tests/coretests/src/android/view/InsetsStateTest.java
@@ -100,7 +100,7 @@
                 .setVisible(true);
         SparseIntArray typeSideMap = new SparseIntArray();
         WindowInsets insets = mState.calculateInsets(new Rect(0, 0, 100, 300), null, false,
-                SOFT_INPUT_ADJUST_RESIZE, 0, 0, TYPE_APPLICATION, WINDOWING_MODE_UNDEFINED,
+                false, SOFT_INPUT_ADJUST_RESIZE, 0, 0, TYPE_APPLICATION, WINDOWING_MODE_UNDEFINED,
                 typeSideMap);
         assertEquals(Insets.of(0, 100, 0, 100), insets.getSystemWindowInsets());
         assertEquals(Insets.of(0, 100, 0, 100), insets.getInsets(Type.all()));
@@ -119,7 +119,8 @@
                 .setFrame(new Rect(0, 100, 100, 300))
                 .setVisible(true);
         WindowInsets insets = mState.calculateInsets(new Rect(0, 0, 100, 300), null, false,
-                SOFT_INPUT_ADJUST_RESIZE, 0, 0, TYPE_APPLICATION, WINDOWING_MODE_UNDEFINED, null);
+                false, SOFT_INPUT_ADJUST_RESIZE, 0, 0, TYPE_APPLICATION, WINDOWING_MODE_UNDEFINED,
+                null);
         assertEquals(100, insets.getStableInsetBottom());
         assertEquals(Insets.of(0, 0, 0, 100), insets.getInsetsIgnoringVisibility(systemBars()));
         assertEquals(Insets.of(0, 0, 0, 200), insets.getSystemWindowInsets());
@@ -137,7 +138,7 @@
                 .setFrame(new Rect(80, 0, 100, 300))
                 .setVisible(true);
         WindowInsets insets = mState.calculateInsets(new Rect(0, 0, 100, 300), null, false,
-                0, 0, 0, TYPE_APPLICATION, WINDOWING_MODE_UNDEFINED, null);
+                false, 0, 0, 0, TYPE_APPLICATION, WINDOWING_MODE_UNDEFINED, null);
         assertEquals(Insets.of(0, 100, 20, 0), insets.getSystemWindowInsets());
         assertEquals(Insets.of(0, 100, 0, 0), insets.getInsets(statusBars()));
         assertEquals(Insets.of(0, 0, 20, 0), insets.getInsets(navigationBars()));
@@ -152,7 +153,7 @@
                 .setFrame(new Rect(80, 0, 100, 300))
                 .setVisible(true);
         WindowInsets insets = mState.calculateInsets(new Rect(0, 0, 100, 300), null, false,
-                0, 0, 0, TYPE_APPLICATION, WINDOWING_MODE_UNDEFINED, null);
+                false, 0, 0, 0, TYPE_APPLICATION, WINDOWING_MODE_UNDEFINED, null);
         assertEquals(Insets.of(0, 100, 20, 0), insets.getSystemWindowInsets());
         assertEquals(Insets.of(0, 100, 0, 0), insets.getInsets(Type.statusBars()));
         assertEquals(Insets.of(0, 0, 20, 0), insets.getInsets(Type.navigationBars()));
@@ -167,7 +168,8 @@
                 .setFrame(new Rect(0, 200, 100, 300))
                 .setVisible(true);
         WindowInsets insets = mState.calculateInsets(new Rect(0, 0, 100, 300), null, false,
-                SOFT_INPUT_ADJUST_NOTHING, 0, 0, TYPE_APPLICATION, WINDOWING_MODE_UNDEFINED, null);
+                false, SOFT_INPUT_ADJUST_NOTHING, 0, 0, TYPE_APPLICATION, WINDOWING_MODE_UNDEFINED,
+                null);
         assertEquals(0, insets.getSystemWindowInsetBottom());
         assertEquals(100, insets.getInsets(ime()).bottom);
         assertTrue(insets.isVisible(ime()));
@@ -182,10 +184,10 @@
                 .setFrame(new Rect(0, 200, 100, 300))
                 .setVisible(true);
         WindowInsets insets = mState.calculateInsets(new Rect(0, 0, 100, 300), null, false,
-                SOFT_INPUT_ADJUST_NOTHING, 0, SYSTEM_UI_FLAG_LAYOUT_STABLE, TYPE_APPLICATION,
+                false, SOFT_INPUT_ADJUST_NOTHING, 0, SYSTEM_UI_FLAG_LAYOUT_STABLE, TYPE_APPLICATION,
                 WINDOWING_MODE_UNDEFINED, null);
         assertEquals(100, insets.getSystemWindowInsetTop());
-        insets = mState.calculateInsets(new Rect(0, 0, 100, 300), null, false,
+        insets = mState.calculateInsets(new Rect(0, 0, 100, 300), null, false, false,
                 SOFT_INPUT_ADJUST_NOTHING, 0, 0 /* legacySystemUiFlags */, TYPE_APPLICATION,
                 WINDOWING_MODE_UNDEFINED, null);
         assertEquals(0, insets.getSystemWindowInsetTop());
@@ -197,10 +199,10 @@
                 .setFrame(new Rect(0, 0, 100, 100))
                 .setVisible(false);
         WindowInsets insets = mState.calculateInsets(new Rect(0, 0, 100, 300), null, false,
-                SOFT_INPUT_ADJUST_NOTHING, FLAG_FULLSCREEN, SYSTEM_UI_FLAG_LAYOUT_STABLE,
+                false, SOFT_INPUT_ADJUST_NOTHING, FLAG_FULLSCREEN, SYSTEM_UI_FLAG_LAYOUT_STABLE,
                 TYPE_APPLICATION, WINDOWING_MODE_UNDEFINED, null);
         assertEquals(0, insets.getSystemWindowInsetTop());
-        insets = mState.calculateInsets(new Rect(0, 0, 100, 300), null, false,
+        insets = mState.calculateInsets(new Rect(0, 0, 100, 300), null, false, false,
                 SOFT_INPUT_ADJUST_NOTHING, 0, 0 /* legacySystemUiFlags */, TYPE_APPLICATION,
                 WINDOWING_MODE_UNDEFINED, null);
         assertEquals(0, insets.getSystemWindowInsetTop());
@@ -212,19 +214,19 @@
                 .setFrame(new Rect(0, 0, 100, 100))
                 .setVisible(true);
         WindowInsets insets = mState.calculateInsets(new Rect(0, 0, 100, 300), null, false,
-                SOFT_INPUT_ADJUST_NOTHING, FLAG_LAYOUT_NO_LIMITS,
+                false, SOFT_INPUT_ADJUST_NOTHING, FLAG_LAYOUT_NO_LIMITS,
                 0 /* legacySystemUiFlags */, TYPE_APPLICATION, WINDOWING_MODE_UNDEFINED, null);
         assertEquals(0, insets.getSystemWindowInsetTop());
         insets = mState.calculateInsets(new Rect(0, 0, 100, 300), null, false,
-                SOFT_INPUT_ADJUST_NOTHING, FLAG_LAYOUT_NO_LIMITS,
+                false, SOFT_INPUT_ADJUST_NOTHING, FLAG_LAYOUT_NO_LIMITS,
                 0 /* legacySystemUiFlags */, TYPE_SYSTEM_ERROR, WINDOWING_MODE_UNDEFINED, null);
         assertEquals(100, insets.getSystemWindowInsetTop());
         insets = mState.calculateInsets(new Rect(0, 0, 100, 300), null, false,
-                SOFT_INPUT_ADJUST_NOTHING, FLAG_LAYOUT_NO_LIMITS,
+                false, SOFT_INPUT_ADJUST_NOTHING, FLAG_LAYOUT_NO_LIMITS,
                 0 /* legacySystemUiFlags */, TYPE_WALLPAPER, WINDOWING_MODE_UNDEFINED, null);
         assertEquals(100, insets.getSystemWindowInsetTop());
         insets = mState.calculateInsets(new Rect(0, 0, 100, 300), null, false,
-                SOFT_INPUT_ADJUST_NOTHING, FLAG_LAYOUT_NO_LIMITS,
+                false, SOFT_INPUT_ADJUST_NOTHING, FLAG_LAYOUT_NO_LIMITS,
                 0 /* legacySystemUiFlags */, TYPE_APPLICATION, WINDOWING_MODE_FREEFORM, null);
         assertEquals(100, insets.getSystemWindowInsetTop());
     }
@@ -266,7 +268,7 @@
                 .setFrame(new Rect(80, 0, 100, 300))
                 .setVisible(true);
         WindowInsets insets = mState.calculateInsets(new Rect(0, 0, 100, 300), null, false,
-                0, 0, 0, TYPE_APPLICATION, WINDOWING_MODE_UNDEFINED, null);
+                false, 0, 0, 0, TYPE_APPLICATION, WINDOWING_MODE_UNDEFINED, null);
         assertEquals(Insets.of(0, 100, 20, 0), insets.getSystemWindowInsets());
         assertEquals(Insets.of(0, 100, 0, 0), insets.getInsets(statusBars()));
         assertEquals(Insets.of(0, 0, 20, 0), insets.getInsets(navigationBars()));
@@ -281,7 +283,7 @@
                 .setFrame(new Rect(80, 0, 100, 300))
                 .setVisible(true);
         WindowInsets insets = mState.calculateInsets(new Rect(0, 0, 100, 300), null, false,
-                0, 0, 0, TYPE_APPLICATION, WINDOWING_MODE_UNDEFINED, null);
+                false, 0, 0, 0, TYPE_APPLICATION, WINDOWING_MODE_UNDEFINED, null);
         assertEquals(Insets.of(0, 100, 20, 0), insets.getSystemWindowInsets());
         assertEquals(Insets.of(0, 100, 0, 0), insets.getInsets(statusBars()));
         assertEquals(Insets.of(0, 0, 20, 0), insets.getInsets(navigationBars()));
@@ -296,7 +298,7 @@
                 .setFrame(new Rect(0, 200, 100, 300))
                 .setVisible(true);
         mState.removeSource(ID_IME);
-        WindowInsets insets = mState.calculateInsets(new Rect(0, 0, 100, 300), null, false,
+        WindowInsets insets = mState.calculateInsets(new Rect(0, 0, 100, 300), null, false, false,
                 SOFT_INPUT_ADJUST_RESIZE, 0, 0, TYPE_APPLICATION, WINDOWING_MODE_UNDEFINED, null);
         assertEquals(0, insets.getSystemWindowInsetBottom());
     }
@@ -528,7 +530,7 @@
                 new Rect(0, 0, 1, 2),
                 new Rect(197, 296, 200, 300),
                 new Rect(197, 296, 200, 300)));
-        DisplayCutout cutout = mState.calculateInsets(new Rect(1, 1, 199, 300), null, false,
+        DisplayCutout cutout = mState.calculateInsets(new Rect(1, 1, 199, 300), null, false, false,
                 SOFT_INPUT_ADJUST_UNSPECIFIED, 0, 0, TYPE_APPLICATION, WINDOWING_MODE_UNDEFINED,
                 new SparseIntArray()).getDisplayCutout();
         assertEquals(0, cutout.getSafeInsetLeft());
@@ -554,7 +556,7 @@
                 new RoundedCorner(POSITION_BOTTOM_RIGHT, 20, 180, 380),
                 new RoundedCorner(POSITION_BOTTOM_LEFT, 20, 20, 380)));
         WindowInsets windowInsets = mState.calculateInsets(new Rect(1, 2, 197, 396), null, false,
-                SOFT_INPUT_ADJUST_UNSPECIFIED, 0, 0, TYPE_APPLICATION,
+                false, SOFT_INPUT_ADJUST_UNSPECIFIED, 0, 0, TYPE_APPLICATION,
                 WINDOWING_MODE_UNDEFINED, new SparseIntArray());
         assertEquals(new RoundedCorner(POSITION_TOP_LEFT, 10, 9, 8),
                 windowInsets.getRoundedCorner(POSITION_TOP_LEFT));
@@ -571,7 +573,7 @@
         mState.setDisplayFrame(new Rect(0, 0, 200, 400));
         mState.setDisplayShape(DisplayShape.createDefaultDisplayShape(200, 400, false));
         WindowInsets windowInsets = mState.calculateInsets(new Rect(10, 20, 200, 400), null, false,
-                SOFT_INPUT_ADJUST_UNSPECIFIED, 0, 0, TYPE_APPLICATION,
+                false, SOFT_INPUT_ADJUST_UNSPECIFIED, 0, 0, TYPE_APPLICATION,
                 WINDOWING_MODE_UNDEFINED, new SparseIntArray());
 
         final DisplayShape expect =
diff --git a/core/tests/coretests/src/com/android/internal/jank/InteractionJankMonitorTest.java b/core/tests/coretests/src/com/android/internal/jank/InteractionJankMonitorTest.java
index 8f83461..b61f995 100644
--- a/core/tests/coretests/src/com/android/internal/jank/InteractionJankMonitorTest.java
+++ b/core/tests/coretests/src/com/android/internal/jank/InteractionJankMonitorTest.java
@@ -79,6 +79,7 @@
 
 import java.lang.reflect.Field;
 import java.lang.reflect.Modifier;
+import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.HashMap;
 import java.util.HashSet;
@@ -93,6 +94,8 @@
     private static final String ENUM_NAME_PREFIX =
             "UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__";
 
+    private static final ArrayList<String> DEPRECATED_VALUES = new ArrayList<>();
+
     private ViewAttachTestActivity mActivity;
     private View mView;
     private HandlerThread mWorker;
@@ -126,6 +129,7 @@
                 CUJ_NOTIFICATION_SHADE_ROW_SWIPE, getEnumName("SHADE_ROW_SWIPE"));
         ENUM_NAME_EXCEPTION_MAP.put(
                 CUJ_NOTIFICATION_SHADE_SCROLL_FLING, getEnumName("SHADE_SCROLL_FLING"));
+        DEPRECATED_VALUES.add(ENUM_NAME_PREFIX + "IME_INSETS_ANIMATION");
     }
 
     private static String getEnumName(String name) {
@@ -239,6 +243,7 @@
 
         Map<Integer, String> enumsMap = Arrays.stream(FrameworkStatsLog.class.getDeclaredFields())
                 .filter(f -> f.getName().startsWith(ENUM_NAME_PREFIX)
+                        && !DEPRECATED_VALUES.contains(f.getName())
                         && Modifier.isStatic(f.getModifiers())
                         && f.getType() == int.class)
                 .collect(Collectors.toMap(this::getIntFieldChecked, Field::getName));
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/SystemWindows.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/SystemWindows.java
index 586a794..5e42782 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/SystemWindows.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/SystemWindows.java
@@ -347,7 +347,8 @@
         @Override
         public void resized(ClientWindowFrames frames, boolean reportDraw,
                 MergedConfiguration newMergedConfiguration, InsetsState insetsState,
-                boolean forceLayout, int displayId, int syncSeqId, boolean dragResizing) {}
+                boolean forceLayout, boolean alwaysConsumeSystemBars, int displayId, int syncSeqId,
+                boolean dragResizing) {}
 
         @Override
         public void insetsControlChanged(InsetsState insetsState,
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/recents/RecentsTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java
index 837f118..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
@@ -573,10 +573,12 @@
                 }
                 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) {
@@ -591,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) {
@@ -612,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..af8ef17 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,12 +618,14 @@
                 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();
             }
         }
-        mStageCoordinator.startShortcutAndTask(shortcutInfo, options1, taskId, options2,
-                splitPosition, splitRatio, remoteTransition, instanceId);
+        mStageCoordinator.startShortcutAndTask(shortcutInfo, activityOptions.toBundle(), taskId,
+                options2, splitPosition, splitRatio, remoteTransition, instanceId);
     }
 
     /**
@@ -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 bf70d48..693bf6c 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;
@@ -217,6 +219,8 @@
     private boolean mIsDropEntering;
     private boolean mIsExiting;
     private boolean mIsRootTranslucent;
+    @VisibleForTesting
+    int mTopStageAfterFoldDismiss;
 
     private DefaultMixedHandler mMixedHandler;
     private final Toast mSplitUnsupportedToast;
@@ -473,6 +477,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 +566,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 +1098,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 +1119,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 +1305,30 @@
         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 StageTaskListener toTop = mainStageVisible ? mMainStage : mSideStage;
+            exitSplitScreen(toTop, EXIT_REASON_SCREEN_LOCKED_SHOW_ON_TOP);
+        }
+
+        // Dismiss split if the flag record any side of stages.
+        if (mTopStageAfterFoldDismiss != STAGE_TYPE_UNDEFINED) {
+            if (ENABLE_SHELL_TRANSITIONS) {
+                // Need manually clear here due to this transition might be aborted due to keyguard
+                // on top and lead to no visible change.
+                clearSplitPairedInRecents(EXIT_REASON_DEVICE_FOLDED);
                 final WindowContainerTransaction wct = new WindowContainerTransaction();
-                prepareExitSplitScreen(dismissTop, wct);
-                mSplitTransitions.startDismissTransition(wct, this, dismissTop,
-                        EXIT_REASON_SCREEN_LOCKED_SHOW_ON_TOP);
+                prepareExitSplitScreen(mTopStageAfterFoldDismiss, wct);
+                mSplitTransitions.startDismissTransition(wct, this,
+                        mTopStageAfterFoldDismiss, EXIT_REASON_DEVICE_FOLDED);
+            } else {
+                exitSplitScreen(
+                        mTopStageAfterFoldDismiss == STAGE_TYPE_MAIN ? mMainStage : mSideStage,
+                        EXIT_REASON_DEVICE_FOLDED);
             }
+            mTopStageAfterFoldDismiss = STAGE_TYPE_UNDEFINED;
         }
     }
 
@@ -1344,15 +1366,8 @@
         if (!mMainStage.isActive() || mIsExiting) return;
 
         onSplitScreenExit();
+        clearSplitPairedInRecents(exitReason);
 
-        mRecentTasks.ifPresent(recentTasks -> {
-            // 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
-            if (shouldBreakPairedTaskInRecents(exitReason) && mShouldUpdateRecents) {
-                recentTasks.removeSplitPair(mMainStage.getTopVisibleChildTaskId());
-                recentTasks.removeSplitPair(mSideStage.getTopVisibleChildTaskId());
-            }
-        });
         mShouldUpdateRecents = false;
         mIsDividerRemoteAnimating = false;
         mSplitRequest = null;
@@ -1479,6 +1494,17 @@
         }
     }
 
+    private void clearSplitPairedInRecents(@ExitReason int exitReason) {
+        if (!shouldBreakPairedTaskInRecents(exitReason) || !mShouldUpdateRecents) return;
+
+        mRecentTasks.ifPresent(recentTasks -> {
+            // 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
+            mMainStage.doForAllChildTasks(taskId -> recentTasks.removeSplitPair(taskId));
+            mSideStage.doForAllChildTasks(taskId -> recentTasks.removeSplitPair(taskId));
+        });
+    }
+
     /**
      * Unlike exitSplitScreen, this takes a stagetype vs an actual stage-reference and populates
      * an existing WindowContainerTransaction (rather than applying immediately). This is intended
@@ -1561,6 +1587,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);
@@ -2204,7 +2232,11 @@
         }
     }
 
-    void updateSurfaces(SurfaceControl.Transaction transaction) {
+    /**
+     * Update surfaces of the split screen layout based on the current state
+     * @param transaction to write the updates to
+     */
+    public void updateSurfaces(SurfaceControl.Transaction transaction) {
         updateSurfaceBounds(mSplitLayout, transaction, /* applyResizingOffset */ false);
         mSplitLayout.update(transaction);
     }
@@ -2223,26 +2255,18 @@
 
     @VisibleForTesting
     void onFoldedStateChanged(boolean folded) {
-        int topStageAfterFoldDismiss = STAGE_TYPE_UNDEFINED;
+        mTopStageAfterFoldDismiss = STAGE_TYPE_UNDEFINED;
         if (!folded) return;
 
-        if (!mMainStage.isActive()) return;
+        if (!isSplitActive() || !isSplitScreenVisible()) return;
 
+        // To avoid split dismiss when user fold the device and unfold to use later, we only
+        // record the flag here and try to dismiss on wakeUp callback to ensure split dismiss
+        // when user interact on phone folded.
         if (mMainStage.isFocused()) {
-            topStageAfterFoldDismiss = STAGE_TYPE_MAIN;
+            mTopStageAfterFoldDismiss = STAGE_TYPE_MAIN;
         } else if (mSideStage.isFocused()) {
-            topStageAfterFoldDismiss = STAGE_TYPE_SIDE;
-        }
-
-        if (ENABLE_SHELL_TRANSITIONS) {
-            final WindowContainerTransaction wct = new WindowContainerTransaction();
-            prepareExitSplitScreen(topStageAfterFoldDismiss, wct);
-            mSplitTransitions.startDismissTransition(wct, this,
-                    topStageAfterFoldDismiss, EXIT_REASON_DEVICE_FOLDED);
-        } else {
-            exitSplitScreen(
-                    topStageAfterFoldDismiss == STAGE_TYPE_MAIN ? mMainStage : mSideStage,
-                    EXIT_REASON_DEVICE_FOLDED);
+            mTopStageAfterFoldDismiss = STAGE_TYPE_SIDE;
         }
     }
 
@@ -2376,6 +2400,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 +2737,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;
             }
@@ -3169,7 +3203,7 @@
         }
 
         @Override
-        public void onNoLongerSupportMultiWindow() {
+        public void onNoLongerSupportMultiWindow(ActivityManager.RunningTaskInfo taskInfo) {
             if (mMainStage.isActive()) {
                 final boolean isMainStage = mMainStageListener == this;
                 if (!ENABLE_SHELL_TRANSITIONS) {
@@ -3184,6 +3218,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..af7bf36 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
@@ -55,6 +55,7 @@
 
 import java.io.PrintWriter;
 import java.util.Optional;
+import java.util.function.Consumer;
 import java.util.function.Predicate;
 
 /**
@@ -81,7 +82,7 @@
 
         void onRootTaskVanished();
 
-        void onNoLongerSupportMultiWindow();
+        void onNoLongerSupportMultiWindow(ActivityManager.RunningTaskInfo taskInfo);
     }
 
     private final Context mContext;
@@ -226,7 +227,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);
@@ -347,6 +348,13 @@
         wct.reorder(mChildrenTaskInfo.get(taskId).token, onTop /* onTop */);
     }
 
+    void doForAllChildTasks(Consumer<Integer> consumer) {
+        for (int i = mChildrenTaskInfo.size() - 1; i >= 0; i--) {
+            final ActivityManager.RunningTaskInfo taskInfo = mChildrenTaskInfo.valueAt(i);
+            consumer.accept(taskInfo.taskId);
+        }
+    }
+
     /** Collects all the current child tasks and prepares transaction to evict them to display. */
     void evictAllChildren(WindowContainerTransaction wct) {
         for (int i = mChildrenTaskInfo.size() - 1; i >= 0; i--) {
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 1d879a13..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
@@ -214,7 +214,8 @@
         @Override
         public void resized(ClientWindowFrames frames, boolean reportDraw,
                 MergedConfiguration mergedConfiguration, InsetsState insetsState,
-                boolean forceLayout, int displayId, int seqId, boolean dragResizing) {
+                boolean forceLayout, boolean alwaysConsumeSystemBars, int displayId, int seqId,
+                boolean dragResizing) {
             final TaskSnapshotWindow snapshot = mOuter.get();
             if (snapshot == null) {
                 return;
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 d9edde1..c5c22de 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
@@ -704,6 +704,9 @@
         if (mPipHandler != null) {
             mPipHandler.syncPipSurfaceState(info, startTransaction, finishTransaction);
         }
+        if (mSplitHandler != null && mSplitHandler.isSplitActive()) {
+            mSplitHandler.updateSurfaces(startTransaction);
+        }
         return mUnfoldHandler.startAnimation(
                 mixed.mTransition, info, startTransaction, finishTransaction, finishCB);
     }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/animation/SplitTaskUnfoldAnimator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/animation/SplitTaskUnfoldAnimator.java
index 21994a9..bb5d546 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/animation/SplitTaskUnfoldAnimator.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/animation/SplitTaskUnfoldAnimator.java
@@ -270,7 +270,6 @@
     @Override
     public void prepareStartTransaction(Transaction transaction) {
         mUnfoldBackgroundController.ensureBackground(transaction);
-        mSplitScreenController.get().get().updateSplitScreenSurfaces(transaction);
     }
 
     @Override
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..208ae84 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/AndroidManifestService.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/AndroidTestTemplate.xml b/libs/WindowManager/Shell/tests/flicker/AndroidTestTemplate.xml
index 991d7b5..c8a9637 100644
--- a/libs/WindowManager/Shell/tests/flicker/AndroidTestTemplate.xml
+++ b/libs/WindowManager/Shell/tests/flicker/AndroidTestTemplate.xml
@@ -93,6 +93,8 @@
                 value="/data/user/0/com.android.server.wm.flicker.pip/files"/>
         <option name="directory-keys"
                 value="/data/user/0/com.android.server.wm.flicker.splitscreen/files"/>
+        <option name="directory-keys"
+            value="/data/user/0/com.android.server.wm.flicker.service/files"/>
         <option name="collect-on-run-ended-only" value="true"/>
         <option name="clean-up" value="true"/>
     </metrics_collector>
diff --git a/libs/WindowManager/Shell/tests/flicker/manifests/AndroidManifestService.xml b/libs/WindowManager/Shell/tests/flicker/manifests/AndroidManifestService.xml
new file mode 100644
index 0000000..c7aca1a
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/manifests/AndroidManifestService.xml
@@ -0,0 +1,24 @@
+<!--
+  ~ 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.
+  -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.android.wm.shell.flicker.service">
+
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+                     android:targetPackage="com.android.wm.shell.flicker.service"
+                     android:label="WindowManager Flicker Service Tests">
+    </instrumentation>
+</manifest>
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
index 6fe88ca..d3f3c5b 100644
--- 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
@@ -18,10 +18,10 @@
 
 import android.platform.test.annotations.Postsubmit
 import android.platform.test.annotations.RequiresDevice
-import android.tools.common.flicker.assertions.FlickerTest
 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
@@ -45,7 +45,6 @@
  *     Swipe right from the bottom of the screen to quick switch back to the app
  * ```
  */
-
 @RequiresDevice
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@@ -152,9 +151,8 @@
     @Test
     fun appWindowBecomesAndStaysVisible() {
         flicker.assertWm {
-            this.isAppWindowInvisible(letterboxApp)
-                .then()
-                .isAppWindowVisible(letterboxApp) }
+            this.isAppWindowInvisible(letterboxApp).then().isAppWindowVisible(letterboxApp)
+        }
     }
 
     /**
@@ -245,7 +243,8 @@
                 .isVisible(ComponentNameMatcher.SNAPSHOT, isOptional = true)
                 .then()
                 .isVisible(letterboxApp)
-                .isVisible(ComponentNameMatcher.LETTERBOX) }
+                .isVisible(ComponentNameMatcher.LETTERBOX)
+        }
     }
 
     /** {@inheritDoc} */
@@ -273,4 +272,4 @@
             )
         }
     }
-}
\ No newline at end of file
+}
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 8a85374..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
@@ -66,11 +66,12 @@
     AutoEnterPipOnGoToHomeTest(flicker) {
     private val portraitDisplayBounds = WindowUtils.getDisplayBounds(Rotation.ROTATION_0)
     /** Second app used to enter split screen mode */
-    private val secondAppForSplitScreen = SimpleAppHelper(
-        instrumentation,
-        ActivityOptions.SplitScreen.Primary.LABEL,
-        ActivityOptions.SplitScreen.Primary.COMPONENT.toFlickerComponent()
-    )
+    private val secondAppForSplitScreen =
+        SimpleAppHelper(
+            instrumentation,
+            ActivityOptions.SplitScreen.Primary.LABEL,
+            ActivityOptions.SplitScreen.Primary.COMPONENT.toFlickerComponent()
+        )
 
     /** Defines the transition used to run the test */
     override val transition: FlickerBuilder.() -> Unit
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/Utils.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/Utils.kt
new file mode 100644
index 0000000..610cede
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/Utils.kt
@@ -0,0 +1,53 @@
+/*
+ * 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
+
+import android.app.Instrumentation
+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.device.apphelpers.MessagingAppHelper
+import android.tools.device.flicker.rules.ChangeDisplayOrientationRule
+import android.tools.device.flicker.rules.LaunchAppRule
+import android.tools.device.flicker.rules.RemoveAllTasksButHomeRule
+import androidx.test.platform.app.InstrumentationRegistry
+import org.junit.rules.RuleChain
+
+object Utils {
+    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())
+    }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/SplitScreenUtils.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/SplitScreenUtils.kt
new file mode 100644
index 0000000..e640dc4
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/SplitScreenUtils.kt
@@ -0,0 +1,387 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.service.splitscreen
+
+import android.app.Instrumentation
+import android.graphics.Point
+import android.os.SystemClock
+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.StandardAppHelper
+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.uiautomator.By
+import androidx.test.uiautomator.BySelector
+import androidx.test.uiautomator.UiDevice
+import androidx.test.uiautomator.UiObject2
+import androidx.test.uiautomator.Until
+import com.android.launcher3.tapl.LauncherInstrumentation
+import com.android.server.wm.flicker.helpers.ImeAppHelper
+import com.android.server.wm.flicker.helpers.NonResizeableAppHelper
+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.wm.shell.flicker.LAUNCHER_UI_PACKAGE_NAME
+import com.android.wm.shell.flicker.SYSTEM_UI_PACKAGE_NAME
+import org.junit.Assert.assertNotNull
+
+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"
+    private const val DIVIDER_BAR = "docked_divider_handle"
+    private const val OVERVIEW_SNAPSHOT = "snapshot"
+    private const val GESTURE_STEP_MS = 16L
+    private val LONG_PRESS_TIME_MS = ViewConfiguration.getLongPressTimeout() * 2L
+    private val SPLIT_DECOR_MANAGER = ComponentNameMatcher("", "SplitDecorManager#")
+
+    private val notificationScrollerSelector: BySelector
+        get() = By.res(SYSTEM_UI_PACKAGE_NAME, NOTIFICATION_SCROLLER)
+    private val notificationContentSelector: BySelector
+        get() = By.text("Flicker Test Notification")
+    private val dividerBarSelector: BySelector
+        get() = By.res(SYSTEM_UI_PACKAGE_NAME, DIVIDER_BAR)
+    private val overviewSnapshotSelector: BySelector
+        get() = By.res(LAUNCHER_UI_PACKAGE_NAME, OVERVIEW_SNAPSHOT)
+
+    fun getPrimary(instrumentation: Instrumentation): StandardAppHelper =
+        SimpleAppHelper(
+            instrumentation,
+            ActivityOptions.SplitScreen.Primary.LABEL,
+            ActivityOptions.SplitScreen.Primary.COMPONENT.toFlickerComponent()
+        )
+
+    fun getSecondary(instrumentation: Instrumentation): StandardAppHelper =
+        SimpleAppHelper(
+            instrumentation,
+            ActivityOptions.SplitScreen.Secondary.LABEL,
+            ActivityOptions.SplitScreen.Secondary.COMPONENT.toFlickerComponent()
+        )
+
+    fun getNonResizeable(instrumentation: Instrumentation): NonResizeableAppHelper =
+        NonResizeableAppHelper(instrumentation)
+
+    fun getSendNotification(instrumentation: Instrumentation): NotificationAppHelper =
+        NotificationAppHelper(instrumentation)
+
+    fun getIme(instrumentation: Instrumentation): ImeAppHelper = ImeAppHelper(instrumentation)
+
+    fun waitForSplitComplete(
+        wmHelper: WindowManagerStateHelper,
+        primaryApp: IComponentMatcher,
+        secondaryApp: IComponentMatcher,
+    ) {
+        wmHelper
+            .StateSyncBuilder()
+            .withWindowSurfaceAppeared(primaryApp)
+            .withWindowSurfaceAppeared(secondaryApp)
+            .withSplitDividerVisible()
+            .waitForAndVerify()
+    }
+
+    fun enterSplit(
+        wmHelper: WindowManagerStateHelper,
+        tapl: LauncherInstrumentation,
+        device: UiDevice,
+        primaryApp: StandardAppHelper,
+        secondaryApp: StandardAppHelper
+    ) {
+        primaryApp.launchViaIntent(wmHelper)
+        secondaryApp.launchViaIntent(wmHelper)
+        tapl.goHome()
+        wmHelper.StateSyncBuilder().withHomeActivityVisible().waitForAndVerify()
+        splitFromOverview(tapl, device)
+        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
+        // 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 = 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)
+    }
+
+    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,
+        wmHelper: WindowManagerStateHelper
+    ) {
+        val displayBounds =
+            wmHelper.currentState.layerState.displays.firstOrNull { !it.isVirtual }?.layerStackSpace
+                ?: error("Display not found")
+
+        // Pull down the notifications
+        device.swipe(
+            displayBounds.centerX(),
+            5,
+            displayBounds.centerX(),
+            displayBounds.bottom,
+            50 /* steps */
+        )
+        SystemClock.sleep(TIMEOUT_MS)
+
+        // Find the target notification
+        val notificationScroller =
+            device.wait(Until.findObject(notificationScrollerSelector), TIMEOUT_MS)
+                ?: error("Unable to find view $notificationScrollerSelector")
+        var notificationContent = notificationScroller.findObject(notificationContentSelector)
+
+        while (notificationContent == null) {
+            device.swipe(
+                displayBounds.centerX(),
+                displayBounds.centerY(),
+                displayBounds.centerX(),
+                displayBounds.centerY() - 150,
+                20 /* steps */
+            )
+            notificationContent = notificationScroller.findObject(notificationContentSelector)
+        }
+
+        // Drag to split
+        val dragStart = notificationContent.visibleCenter
+        val dragMiddle = Point(dragStart.x + 50, dragStart.y)
+        val dragEnd = Point(displayBounds.width / 4, displayBounds.width / 4)
+        val downTime = SystemClock.uptimeMillis()
+
+        touch(instrumentation, MotionEvent.ACTION_DOWN, downTime, downTime, TIMEOUT_MS, dragStart)
+        // It needs a horizontal movement to trigger the drag
+        touchMove(
+            instrumentation,
+            downTime,
+            SystemClock.uptimeMillis(),
+            DRAG_DURATION_MS,
+            dragStart,
+            dragMiddle
+        )
+        touchMove(
+            instrumentation,
+            downTime,
+            SystemClock.uptimeMillis(),
+            DRAG_DURATION_MS,
+            dragMiddle,
+            dragEnd
+        )
+        // Wait for a while to start splitting
+        SystemClock.sleep(TIMEOUT_MS)
+        touch(
+            instrumentation,
+            MotionEvent.ACTION_UP,
+            downTime,
+            SystemClock.uptimeMillis(),
+            GESTURE_STEP_MS,
+            dragEnd
+        )
+        SystemClock.sleep(TIMEOUT_MS)
+    }
+
+    fun touch(
+        instrumentation: Instrumentation,
+        action: Int,
+        downTime: Long,
+        eventTime: Long,
+        duration: Long,
+        point: Point
+    ) {
+        val motionEvent =
+            MotionEvent.obtain(downTime, eventTime, action, point.x.toFloat(), point.y.toFloat(), 0)
+        motionEvent.source = InputDevice.SOURCE_TOUCHSCREEN
+        instrumentation.uiAutomation.injectInputEvent(motionEvent, true)
+        motionEvent.recycle()
+        SystemClock.sleep(duration)
+    }
+
+    fun touchMove(
+        instrumentation: Instrumentation,
+        downTime: Long,
+        eventTime: Long,
+        duration: Long,
+        from: Point,
+        to: Point
+    ) {
+        val steps: Long = duration / GESTURE_STEP_MS
+        var currentTime = eventTime
+        var currentX = from.x.toFloat()
+        var currentY = from.y.toFloat()
+        val stepX = (to.x.toFloat() - from.x.toFloat()) / steps.toFloat()
+        val stepY = (to.y.toFloat() - from.y.toFloat()) / steps.toFloat()
+
+        for (i in 1..steps) {
+            val motionMove =
+                MotionEvent.obtain(
+                    downTime,
+                    currentTime,
+                    MotionEvent.ACTION_MOVE,
+                    currentX,
+                    currentY,
+                    0
+                )
+            motionMove.source = InputDevice.SOURCE_TOUCHSCREEN
+            instrumentation.uiAutomation.injectInputEvent(motionMove, true)
+            motionMove.recycle()
+
+            currentTime += GESTURE_STEP_MS
+            if (i == steps - 1) {
+                currentX = to.x.toFloat()
+                currentY = to.y.toFloat()
+            } else {
+                currentX += stepX
+                currentY += stepY
+            }
+            SystemClock.sleep(GESTURE_STEP_MS)
+        }
+    }
+
+    fun createShortcutOnHotseatIfNotExist(tapl: LauncherInstrumentation, appName: String) {
+        tapl.workspace.deleteAppIcon(tapl.workspace.getHotseatAppIcon(0))
+        val allApps = tapl.workspace.switchToAllApps()
+        allApps.freeze()
+        try {
+            allApps.getAppIcon(appName).dragToHotseat(0)
+        } finally {
+            allApps.unfreeze()
+        }
+    }
+
+    fun dragDividerToResizeAndWait(device: UiDevice, wmHelper: WindowManagerStateHelper) {
+        val displayBounds =
+            wmHelper.currentState.layerState.displays.firstOrNull { !it.isVirtual }?.layerStackSpace
+                ?: error("Display not found")
+        val dividerBar = device.wait(Until.findObject(dividerBarSelector), TIMEOUT_MS)
+        dividerBar.drag(Point(displayBounds.width * 1 / 3, displayBounds.height * 2 / 3), 200)
+
+        wmHelper
+            .StateSyncBuilder()
+            .withWindowSurfaceDisappeared(SPLIT_DECOR_MANAGER)
+            .waitForAndVerify()
+    }
+
+    fun dragDividerToDismissSplit(
+        device: UiDevice,
+        wmHelper: WindowManagerStateHelper,
+        dragToRight: Boolean,
+        dragToBottom: Boolean
+    ) {
+        val displayBounds =
+            wmHelper.currentState.layerState.displays.firstOrNull { !it.isVirtual }?.layerStackSpace
+                ?: error("Display not found")
+        val dividerBar = device.wait(Until.findObject(dividerBarSelector), TIMEOUT_MS)
+        dividerBar.drag(
+            Point(
+                if (dragToRight) {
+                    displayBounds.width * 4 / 5
+                } else {
+                    displayBounds.width * 1 / 5
+                },
+                if (dragToBottom) {
+                    displayBounds.height * 4 / 5
+                } else {
+                    displayBounds.height * 1 / 5
+                }
+            )
+        )
+    }
+
+    fun doubleTapDividerToSwitch(device: UiDevice) {
+        val dividerBar = device.wait(Until.findObject(dividerBarSelector), TIMEOUT_MS)
+        val interval =
+            (ViewConfiguration.getDoubleTapTimeout() + ViewConfiguration.getDoubleTapMinTime()) / 2
+        dividerBar.click()
+        SystemClock.sleep(interval.toLong())
+        dividerBar.click()
+    }
+
+    fun copyContentInSplit(
+        instrumentation: Instrumentation,
+        device: UiDevice,
+        sourceApp: IComponentNameMatcher,
+        destinationApp: IComponentNameMatcher,
+    ) {
+        // Copy text from sourceApp
+        val textView =
+            device.wait(
+                Until.findObject(By.res(sourceApp.packageName, "SplitScreenTest")),
+                TIMEOUT_MS
+            )
+        assertNotNull("Unable to find the TextView", textView)
+        textView.click(LONG_PRESS_TIME_MS)
+
+        val copyBtn = device.wait(Until.findObject(By.text("Copy")), TIMEOUT_MS)
+        assertNotNull("Unable to find the copy button", copyBtn)
+        copyBtn.click()
+
+        // Paste text to destinationApp
+        val editText =
+            device.wait(
+                Until.findObject(By.res(destinationApp.packageName, "plain_text_input")),
+                TIMEOUT_MS
+            )
+        assertNotNull("Unable to find the EditText", editText)
+        editText.click(LONG_PRESS_TIME_MS)
+
+        val pasteBtn = device.wait(Until.findObject(By.text("Paste")), TIMEOUT_MS)
+        assertNotNull("Unable to find the paste button", pasteBtn)
+        pasteBtn.click()
+
+        // Verify text
+        if (!textView.text.contentEquals(editText.text)) {
+            error("Fail to copy content in split")
+        }
+    }
+}
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
index 76ad6b9..5bfc889 100644
--- 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
@@ -23,6 +23,7 @@
 import androidx.test.platform.app.InstrumentationRegistry
 import androidx.test.uiautomator.UiDevice
 import com.android.launcher3.tapl.LauncherInstrumentation
+import com.android.wm.shell.flicker.service.Utils
 import org.junit.After
 import org.junit.Before
 import org.junit.Ignore
@@ -42,9 +43,7 @@
     private val secondaryApp = SplitScreenUtils.getSecondary(instrumentation)
     private val textEditApp = SplitScreenUtils.getIme(instrumentation)
 
-    @Rule
-    @JvmField
-    val testSetupRule = SplitScreenUtils.testSetupRule({ NavBar.MODE_GESTURAL }, { rotation })
+    @Rule @JvmField val testSetupRule = Utils.testSetupRule(NavBar.MODE_GESTURAL, rotation)
 
     @Before
     fun setup() {
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
index 25182b4..d07daff 100644
--- 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
@@ -23,6 +23,7 @@
 import androidx.test.platform.app.InstrumentationRegistry
 import androidx.test.uiautomator.UiDevice
 import com.android.launcher3.tapl.LauncherInstrumentation
+import com.android.wm.shell.flicker.service.Utils
 import org.junit.After
 import org.junit.Before
 import org.junit.Ignore
@@ -40,9 +41,7 @@
     private val primaryApp = SplitScreenUtils.getPrimary(instrumentation)
     private val secondaryApp = SplitScreenUtils.getSecondary(instrumentation)
 
-    @Rule
-    @JvmField
-    val testSetupRule = SplitScreenUtils.testSetupRule({ NavBar.MODE_GESTURAL }, { rotation })
+    @Rule @JvmField val testSetupRule = Utils.testSetupRule(NavBar.MODE_GESTURAL, rotation)
 
     @Before
     fun setup() {
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
index 000b628..d428dea 100644
--- 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
@@ -23,6 +23,7 @@
 import androidx.test.platform.app.InstrumentationRegistry
 import androidx.test.uiautomator.UiDevice
 import com.android.launcher3.tapl.LauncherInstrumentation
+import com.android.wm.shell.flicker.service.Utils
 import org.junit.After
 import org.junit.Before
 import org.junit.Ignore
@@ -40,9 +41,7 @@
     private val primaryApp = SplitScreenUtils.getPrimary(instrumentation)
     private val secondaryApp = SplitScreenUtils.getSecondary(instrumentation)
 
-    @Rule
-    @JvmField
-    val testSetupRule = SplitScreenUtils.testSetupRule({ NavBar.MODE_GESTURAL }, { rotation })
+    @Rule @JvmField val testSetupRule = Utils.testSetupRule(NavBar.MODE_GESTURAL, rotation)
 
     @Before
     fun setup() {
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
index dd9ff3c..dc2a6ac 100644
--- 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
@@ -23,6 +23,7 @@
 import androidx.test.platform.app.InstrumentationRegistry
 import androidx.test.uiautomator.UiDevice
 import com.android.launcher3.tapl.LauncherInstrumentation
+import com.android.wm.shell.flicker.service.Utils
 import org.junit.After
 import org.junit.Before
 import org.junit.Ignore
@@ -40,9 +41,7 @@
     private val primaryApp = SplitScreenUtils.getPrimary(instrumentation)
     private val secondaryApp = SplitScreenUtils.getSecondary(instrumentation)
 
-    @Rule
-    @JvmField
-    val testSetupRule = SplitScreenUtils.testSetupRule({ NavBar.MODE_GESTURAL }, { rotation })
+    @Rule @JvmField val testSetupRule = Utils.testSetupRule(NavBar.MODE_GESTURAL, rotation)
 
     @Before
     fun setup() {
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
index 4bbb9aa..677aeb07 100644
--- 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
@@ -23,6 +23,7 @@
 import androidx.test.platform.app.InstrumentationRegistry
 import androidx.test.uiautomator.UiDevice
 import com.android.launcher3.tapl.LauncherInstrumentation
+import com.android.wm.shell.flicker.service.Utils
 import org.junit.After
 import org.junit.Before
 import org.junit.Ignore
@@ -40,9 +41,7 @@
     private val primaryApp = SplitScreenUtils.getPrimary(instrumentation)
     private val secondaryApp = SplitScreenUtils.getSecondary(instrumentation)
 
-    @Rule
-    @JvmField
-    val testSetupRule = SplitScreenUtils.testSetupRule({ NavBar.MODE_GESTURAL }, { rotation })
+    @Rule @JvmField val testSetupRule = Utils.testSetupRule(NavBar.MODE_GESTURAL, rotation)
 
     @Before
     fun setup() {
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
index a2b7526..f4f6878 100644
--- 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
@@ -23,6 +23,7 @@
 import androidx.test.platform.app.InstrumentationRegistry
 import androidx.test.uiautomator.UiDevice
 import com.android.launcher3.tapl.LauncherInstrumentation
+import com.android.wm.shell.flicker.service.Utils
 import org.junit.After
 import org.junit.Before
 import org.junit.Ignore
@@ -41,9 +42,7 @@
     private val secondaryApp = SplitScreenUtils.getSecondary(instrumentation)
     private val sendNotificationApp = SplitScreenUtils.getSendNotification(instrumentation)
 
-    @Rule
-    @JvmField
-    val testSetupRule = SplitScreenUtils.testSetupRule({ NavBar.MODE_GESTURAL }, { rotation })
+    @Rule @JvmField val testSetupRule = Utils.testSetupRule(NavBar.MODE_GESTURAL, rotation)
 
     @Before
     fun setup() {
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
index 1ccd813..36a458f 100644
--- 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
@@ -23,6 +23,7 @@
 import androidx.test.platform.app.InstrumentationRegistry
 import androidx.test.uiautomator.UiDevice
 import com.android.launcher3.tapl.LauncherInstrumentation
+import com.android.wm.shell.flicker.service.Utils
 import org.junit.After
 import org.junit.Assume
 import org.junit.Before
@@ -41,9 +42,7 @@
     private val primaryApp = SplitScreenUtils.getPrimary(instrumentation)
     private val secondaryApp = SplitScreenUtils.getSecondary(instrumentation)
 
-    @Rule
-    @JvmField
-    val testSetupRule = SplitScreenUtils.testSetupRule({ NavBar.MODE_GESTURAL }, { rotation })
+    @Rule @JvmField val testSetupRule = Utils.testSetupRule(NavBar.MODE_GESTURAL, rotation)
 
     @Before
     fun setup() {
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
index 664786b..322f711 100644
--- 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
@@ -23,6 +23,7 @@
 import androidx.test.platform.app.InstrumentationRegistry
 import androidx.test.uiautomator.UiDevice
 import com.android.launcher3.tapl.LauncherInstrumentation
+import com.android.wm.shell.flicker.service.Utils
 import org.junit.After
 import org.junit.Before
 import org.junit.Ignore
@@ -40,9 +41,7 @@
     private val primaryApp = SplitScreenUtils.getPrimary(instrumentation)
     private val secondaryApp = SplitScreenUtils.getSecondary(instrumentation)
 
-    @Rule
-    @JvmField
-    val testSetupRule = SplitScreenUtils.testSetupRule({ NavBar.MODE_GESTURAL }, { rotation })
+    @Rule @JvmField val testSetupRule = Utils.testSetupRule(NavBar.MODE_GESTURAL, rotation)
 
     @Before
     fun setup() {
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
index 88fd084..f164451 100644
--- 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
@@ -23,6 +23,7 @@
 import androidx.test.platform.app.InstrumentationRegistry
 import androidx.test.uiautomator.UiDevice
 import com.android.launcher3.tapl.LauncherInstrumentation
+import com.android.wm.shell.flicker.service.Utils
 import org.junit.After
 import org.junit.Before
 import org.junit.Ignore
@@ -40,9 +41,7 @@
     private val primaryApp = SplitScreenUtils.getPrimary(instrumentation)
     private val secondaryApp = SplitScreenUtils.getSecondary(instrumentation)
 
-    @Rule
-    @JvmField
-    val testSetupRule = SplitScreenUtils.testSetupRule({ NavBar.MODE_GESTURAL }, { rotation })
+    @Rule @JvmField val testSetupRule = Utils.testSetupRule(NavBar.MODE_GESTURAL, rotation)
 
     @Before
     fun setup() {
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SplitScreenUtils.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SplitScreenUtils.kt
index 83a18e8..3831c65 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SplitScreenUtils.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SplitScreenUtils.kt
@@ -19,25 +19,15 @@
 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
@@ -52,7 +42,6 @@
 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
 
 object SplitScreenUtils {
     private const val TIMEOUT_MS = 3_000L
@@ -73,30 +62,6 @@
     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,
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
index e5501f4..805d987 100644
--- 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
@@ -25,6 +25,7 @@
 import androidx.test.platform.app.InstrumentationRegistry
 import androidx.test.uiautomator.UiDevice
 import com.android.launcher3.tapl.LauncherInstrumentation
+import com.android.wm.shell.flicker.service.Utils
 import org.junit.After
 import org.junit.Before
 import org.junit.Ignore
@@ -42,9 +43,7 @@
     private val primaryApp = SplitScreenUtils.getPrimary(instrumentation)
     private val secondaryApp = SplitScreenUtils.getSecondary(instrumentation)
 
-    @Rule
-    @JvmField
-    val testSetupRule = SplitScreenUtils.testSetupRule({ NavBar.MODE_GESTURAL }, { rotation })
+    @Rule @JvmField val testSetupRule = Utils.testSetupRule(NavBar.MODE_GESTURAL, rotation)
 
     @Before
     fun setup() {
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
index b3f1e87..4229ebb 100644
--- 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
@@ -23,6 +23,7 @@
 import androidx.test.platform.app.InstrumentationRegistry
 import androidx.test.uiautomator.UiDevice
 import com.android.launcher3.tapl.LauncherInstrumentation
+import com.android.wm.shell.flicker.service.Utils
 import org.junit.After
 import org.junit.Before
 import org.junit.Ignore
@@ -41,9 +42,7 @@
     private val secondaryApp = SplitScreenUtils.getSecondary(instrumentation)
     private val thirdApp = SplitScreenUtils.getNonResizeable(instrumentation)
 
-    @Rule
-    @JvmField
-    val testSetupRule = SplitScreenUtils.testSetupRule({ NavBar.MODE_GESTURAL }, { rotation })
+    @Rule @JvmField val testSetupRule = Utils.testSetupRule(NavBar.MODE_GESTURAL, rotation)
 
     @Before
     fun setup() {
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
index d112116..f2d56b9 100644
--- 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
@@ -23,6 +23,7 @@
 import androidx.test.platform.app.InstrumentationRegistry
 import androidx.test.uiautomator.UiDevice
 import com.android.launcher3.tapl.LauncherInstrumentation
+import com.android.wm.shell.flicker.service.Utils
 import org.junit.After
 import org.junit.Before
 import org.junit.Ignore
@@ -40,9 +41,7 @@
     private val primaryApp = SplitScreenUtils.getPrimary(instrumentation)
     private val secondaryApp = SplitScreenUtils.getSecondary(instrumentation)
 
-    @Rule
-    @JvmField
-    val testSetupRule = SplitScreenUtils.testSetupRule({ NavBar.MODE_GESTURAL }, { rotation })
+    @Rule @JvmField val testSetupRule = Utils.testSetupRule(NavBar.MODE_GESTURAL, rotation)
 
     @Before
     fun setup() {
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
index 9ab924c..d44d177 100644
--- 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
@@ -23,6 +23,7 @@
 import androidx.test.platform.app.InstrumentationRegistry
 import androidx.test.uiautomator.UiDevice
 import com.android.launcher3.tapl.LauncherInstrumentation
+import com.android.wm.shell.flicker.service.Utils
 import org.junit.After
 import org.junit.Before
 import org.junit.Ignore
@@ -40,9 +41,7 @@
     private val primaryApp = SplitScreenUtils.getPrimary(instrumentation)
     private val secondaryApp = SplitScreenUtils.getSecondary(instrumentation)
 
-    @Rule
-    @JvmField
-    val testSetupRule = SplitScreenUtils.testSetupRule({ NavBar.MODE_GESTURAL }, { rotation })
+    @Rule @JvmField val testSetupRule = Utils.testSetupRule(NavBar.MODE_GESTURAL, rotation)
 
     @Before
     fun setup() {
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
index b694dfa..e2c6ca6 100644
--- 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
@@ -23,6 +23,7 @@
 import androidx.test.platform.app.InstrumentationRegistry
 import androidx.test.uiautomator.UiDevice
 import com.android.launcher3.tapl.LauncherInstrumentation
+import com.android.wm.shell.flicker.service.Utils
 import org.junit.After
 import org.junit.Before
 import org.junit.Ignore
@@ -42,9 +43,7 @@
     private val thirdApp = SplitScreenUtils.getIme(instrumentation)
     private val fourthApp = SplitScreenUtils.getSendNotification(instrumentation)
 
-    @Rule
-    @JvmField
-    val testSetupRule = SplitScreenUtils.testSetupRule({ NavBar.MODE_GESTURAL }, { rotation })
+    @Rule @JvmField val testSetupRule = Utils.testSetupRule(NavBar.MODE_GESTURAL, rotation)
 
     @Before
     fun setup() {
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
index f78b788..df98d8f 100644
--- 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
@@ -23,6 +23,7 @@
 import androidx.test.platform.app.InstrumentationRegistry
 import androidx.test.uiautomator.UiDevice
 import com.android.launcher3.tapl.LauncherInstrumentation
+import com.android.wm.shell.flicker.service.Utils
 import org.junit.After
 import org.junit.Before
 import org.junit.Ignore
@@ -40,8 +41,7 @@
 
     @Rule
     @JvmField
-    val testSetupRule =
-        SplitScreenUtils.testSetupRule({ NavBar.MODE_GESTURAL }, { Rotation.ROTATION_0 })
+    val testSetupRule = Utils.testSetupRule(NavBar.MODE_GESTURAL, Rotation.ROTATION_0)
 
     @Before
     fun setup() {
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/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 9c68aa4..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.splitscreen.SplitScreenBase
 import com.android.wm.shell.flicker.SplitScreenUtils
+import com.android.wm.shell.flicker.splitscreen.SplitScreenBase
 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 21ac783..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.splitscreen.SplitScreenBase
 import com.android.wm.shell.flicker.SplitScreenUtils
+import com.android.wm.shell.flicker.splitscreen.SplitScreenBase
 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 931bff6..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.splitscreen.SplitScreenBase
 import com.android.wm.shell.flicker.SplitScreenUtils
+import com.android.wm.shell.flicker.splitscreen.SplitScreenBase
 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 7fa2c0b..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.splitscreen.SplitScreenBase
 import com.android.wm.shell.flicker.SplitScreenUtils
+import com.android.wm.shell.flicker.splitscreen.SplitScreenBase
 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 952051f..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.splitscreen.SplitScreenBase
 import com.android.wm.shell.flicker.SplitScreenUtils
+import com.android.wm.shell.flicker.splitscreen.SplitScreenBase
 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 1de1c0c..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.splitscreen.SplitScreenBase
 import com.android.wm.shell.flicker.SplitScreenUtils
+import com.android.wm.shell.flicker.splitscreen.SplitScreenBase
 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 929c7ea..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.splitscreen.SplitScreenBase
 import com.android.wm.shell.flicker.SplitScreenUtils
+import com.android.wm.shell.flicker.splitscreen.SplitScreenBase
 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 9f829c9..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.splitscreen.SplitScreenBase
 import com.android.wm.shell.flicker.SplitScreenUtils
+import com.android.wm.shell.flicker.splitscreen.SplitScreenBase
 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 1d5518f..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.splitscreen.SplitScreenBase
 import com.android.wm.shell.flicker.SplitScreenUtils
+import com.android.wm.shell.flicker.splitscreen.SplitScreenBase
 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 a7fb93e..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.splitscreen.SplitScreenBase
 import com.android.wm.shell.flicker.SplitScreenUtils
+import com.android.wm.shell.flicker.splitscreen.SplitScreenBase
 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 8358aff..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.splitscreen.SplitScreenBase
 import com.android.wm.shell.flicker.SplitScreenUtils
+import com.android.wm.shell.flicker.splitscreen.SplitScreenBase
 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 b63c765..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.splitscreen.SplitScreenBase
 import com.android.wm.shell.flicker.SplitScreenUtils
+import com.android.wm.shell.flicker.splitscreen.SplitScreenBase
 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 ce5a409b..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.splitscreen.SplitScreenBase
 import com.android.wm.shell.flicker.SplitScreenUtils
+import com.android.wm.shell.flicker.splitscreen.SplitScreenBase
 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 9821bfa..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.splitscreen.SplitScreenBase
 import com.android.wm.shell.flicker.SplitScreenUtils
+import com.android.wm.shell.flicker.splitscreen.SplitScreenBase
 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,8 +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 4fc4627..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.splitscreen.SplitScreenBase
 import com.android.wm.shell.flicker.SplitScreenUtils
+import com.android.wm.shell.flicker.splitscreen.SplitScreenBase
 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/StageCoordinatorTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java
index e59d09c..cc4db22 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java
@@ -347,13 +347,20 @@
     }
 
     @Test
-    public void testExitSplitScreenAfterFolded() {
-        when(mMainStage.isActive()).thenReturn(true);
+    public void testExitSplitScreenAfterFoldedAndWakeUp() {
         when(mMainStage.isFocused()).thenReturn(true);
         when(mMainStage.getTopVisibleChildTaskId()).thenReturn(INVALID_TASK_ID);
+        mSideStage.mRootTaskInfo = new TestRunningTaskInfoBuilder().setVisible(true).build();
+        mMainStage.mRootTaskInfo = new TestRunningTaskInfoBuilder().setVisible(true).build();
+        when(mStageCoordinator.isSplitActive()).thenReturn(true);
+        when(mStageCoordinator.isSplitScreenVisible()).thenReturn(true);
 
         mStageCoordinator.onFoldedStateChanged(true);
 
+        assertEquals(mStageCoordinator.mTopStageAfterFoldDismiss, STAGE_TYPE_MAIN);
+
+        mStageCoordinator.onFinishedWakingUp();
+
         if (Transitions.ENABLE_SHELL_TRANSITIONS) {
             verify(mTaskOrganizer).startNewTransition(eq(TRANSIT_SPLIT_DISMISS), notNull());
         } else {
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/libs/hwui/jni/BitmapRegionDecoder.cpp b/libs/hwui/jni/BitmapRegionDecoder.cpp
index 4c9a23d..740988f 100644
--- a/libs/hwui/jni/BitmapRegionDecoder.cpp
+++ b/libs/hwui/jni/BitmapRegionDecoder.cpp
@@ -90,8 +90,8 @@
                                            requireUnpremul, prefColorSpace);
     }
 
-    bool decodeGainmapRegion(sp<uirenderer::Gainmap>* outGainmap, const SkIRect& desiredSubset,
-                             int sampleSize, bool requireUnpremul) {
+    bool decodeGainmapRegion(sp<uirenderer::Gainmap>* outGainmap, int outWidth, int outHeight,
+                             const SkIRect& desiredSubset, int sampleSize, bool requireUnpremul) {
         SkColorType decodeColorType = mGainmapBRD->computeOutputColorType(kN32_SkColorType);
         sk_sp<SkColorSpace> decodeColorSpace =
                 mGainmapBRD->computeOutputColorSpace(decodeColorType, nullptr);
@@ -109,9 +109,8 @@
         // kPremul_SkAlphaType is used just as a placeholder as it doesn't change the underlying
         // allocation type. RecyclingClippingPixelAllocator will populate this with the
         // actual alpha type in either allocPixelRef() or copyIfNecessary()
-        sk_sp<Bitmap> nativeBitmap = Bitmap::allocateHeapBitmap(
-                SkImageInfo::Make(desiredSubset.width(), desiredSubset.height(), decodeColorType,
-                                  kPremul_SkAlphaType, decodeColorSpace));
+        sk_sp<Bitmap> nativeBitmap = Bitmap::allocateHeapBitmap(SkImageInfo::Make(
+                outWidth, outHeight, decodeColorType, kPremul_SkAlphaType, decodeColorSpace));
         if (!nativeBitmap) {
             ALOGE("OOM allocating Bitmap for Gainmap");
             return false;
@@ -134,9 +133,12 @@
         return true;
     }
 
-    SkIRect calculateGainmapRegion(const SkIRect& mainImageRegion) {
+    SkIRect calculateGainmapRegion(const SkIRect& mainImageRegion, int* inOutWidth,
+                                   int* inOutHeight) {
         const float scaleX = ((float)mGainmapBRD->width()) / mMainImageBRD->width();
         const float scaleY = ((float)mGainmapBRD->height()) / mMainImageBRD->height();
+        *inOutWidth *= scaleX;
+        *inOutHeight *= scaleY;
         // TODO: Account for rounding error?
         return SkIRect::MakeLTRB(mainImageRegion.left() * scaleX, mainImageRegion.top() * scaleY,
                                  mainImageRegion.right() * scaleX,
@@ -328,21 +330,16 @@
     sp<uirenderer::Gainmap> gainmap;
     bool hasGainmap = brd->hasGainmap();
     if (hasGainmap) {
-        SkIRect adjustedSubset{};
+        int gainmapWidth = bitmap.width();
+        int gainmapHeight = bitmap.height();
         if (javaBitmap) {
-            // Clamp to the width/height of the recycled bitmap in case the reused bitmap
-            // was too small for the specified rectangle, in which case we need to clip
-            adjustedSubset = SkIRect::MakeXYWH(inputX, inputY,
-                                               std::min(subset.width(), recycledBitmap->width()),
-                                               std::min(subset.height(), recycledBitmap->height()));
-        } else {
-            // We are not recycling, so use the decoded width/height for calculating the gainmap
-            // subset instead to ensure the gainmap region proportionally matches
-            adjustedSubset = SkIRect::MakeXYWH(std::max(0, inputX), std::max(0, inputY),
-                                               bitmap.width(), bitmap.height());
+            // If we are recycling we must match the inBitmap's relative dimensions
+            gainmapWidth = recycledBitmap->width();
+            gainmapHeight = recycledBitmap->height();
         }
-        SkIRect gainmapSubset = brd->calculateGainmapRegion(adjustedSubset);
-        if (!brd->decodeGainmapRegion(&gainmap, gainmapSubset, sampleSize, requireUnpremul)) {
+        SkIRect gainmapSubset = brd->calculateGainmapRegion(subset, &gainmapWidth, &gainmapHeight);
+        if (!brd->decodeGainmapRegion(&gainmap, gainmapWidth, gainmapHeight, gainmapSubset,
+                                      sampleSize, requireUnpremul)) {
             // If there is an error decoding Gainmap - we don't fail. We just don't provide Gainmap
             hasGainmap = false;
         }
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/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/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/ScreenshotActivity.kt b/packages/SettingsLib/tests/robotests/fragment/src/main/java/org/robolectric/shadows/androidx/fragment/package-info.java
similarity index 65%
copy from packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/ScreenshotActivity.kt
copy to packages/SettingsLib/tests/robotests/fragment/src/main/java/org/robolectric/shadows/androidx/fragment/package-info.java
index 2a55a80..dd89441 100644
--- a/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/ScreenshotActivity.kt
+++ b/packages/SettingsLib/tests/robotests/fragment/src/main/java/org/robolectric/shadows/androidx/fragment/package-info.java
@@ -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,9 +14,10 @@
  * limitations under the License.
  */
 
-package com.android.systemui.testing.screenshot
-
-import androidx.activity.ComponentActivity
-
-/** The Activity that is launched and whose content is set for screenshot tests. */
-class ScreenshotActivity : ComponentActivity()
+/**
+ * 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/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/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/ScreenshotActivity.kt b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/package-info.java
similarity index 66%
rename from packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/ScreenshotActivity.kt
rename to packages/SettingsLib/tests/robotests/src/com/android/settingslib/package-info.java
index 2a55a80..9e9725f 100644
--- a/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/ScreenshotActivity.kt
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/package-info.java
@@ -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,9 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.systemui.testing.screenshot
+@LooperMode(LooperMode.Mode.LEGACY)
+package com.android.settingslib;
 
-import androidx.activity.ComponentActivity
-
-/** The Activity that is launched and whose content is set for screenshot tests. */
-class ScreenshotActivity : ComponentActivity()
+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/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/compose/testing/Android.bp b/packages/SystemUI/compose/testing/Android.bp
deleted file mode 100644
index 555f42e..0000000
--- a/packages/SystemUI/compose/testing/Android.bp
+++ /dev/null
@@ -1,43 +0,0 @@
-// Copyright (C) 2022 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//      http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package {
-    // See: http://go/android-license-faq
-    // A large-scale-change added 'default_applicable_licenses' to import
-    // all of the 'license_kinds' from "frameworks_base_packages_SystemUI_license"
-    // to get the below license kinds:
-    //   SPDX-license-identifier-Apache-2.0
-    default_applicable_licenses: ["frameworks_base_packages_SystemUI_license"],
-}
-
-android_library {
-    name: "SystemUIComposeTesting",
-    manifest: "AndroidManifest.xml",
-
-    srcs: [
-        "src/**/*.kt",
-    ],
-
-    static_libs: [
-        "PlatformComposeCore",
-        "SystemUIScreenshotLib",
-
-        "androidx.compose.runtime_runtime",
-        "androidx.compose.material3_material3",
-        "androidx.compose.ui_ui-test-junit4",
-        "androidx.compose.ui_ui-test-manifest",
-    ],
-
-    kotlincflags: ["-Xjvm-default=all"],
-}
diff --git a/packages/SystemUI/compose/testing/AndroidManifest.xml b/packages/SystemUI/compose/testing/AndroidManifest.xml
deleted file mode 100644
index b1f7c3b..0000000
--- a/packages/SystemUI/compose/testing/AndroidManifest.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-     Copyright (C) 2022 The Android Open Source Project
-
-     Licensed under the Apache License, Version 2.0 (the "License");
-     you may not use this file except in compliance with the License.
-     You may obtain a copy of the License at
-
-          http://www.apache.org/licenses/LICENSE-2.0
-
-     Unless required by applicable law or agreed to in writing, software
-     distributed under the License is distributed on an "AS IS" BASIS,
-     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-     See the License for the specific language governing permissions and
-     limitations under the License.
--->
-
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:tools="http://schemas.android.com/tools"
-    package="com.android.systemui.testing.compose">
-    <application
-        android:appComponentFactory="androidx.core.app.AppComponentFactory"
-        tools:replace="android:appComponentFactory">
-    </application>
-</manifest>
diff --git a/packages/SystemUI/compose/testing/src/com/android/systemui/testing/compose/ComposeScreenshotTestRule.kt b/packages/SystemUI/compose/testing/src/com/android/systemui/testing/compose/ComposeScreenshotTestRule.kt
deleted file mode 100644
index cb9e53c..0000000
--- a/packages/SystemUI/compose/testing/src/com/android/systemui/testing/compose/ComposeScreenshotTestRule.kt
+++ /dev/null
@@ -1,95 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.testing.compose
-
-import androidx.compose.material3.MaterialTheme
-import androidx.compose.material3.Surface
-import androidx.compose.runtime.Composable
-import androidx.compose.ui.platform.ViewRootForTest
-import androidx.compose.ui.test.junit4.createAndroidComposeRule
-import androidx.compose.ui.test.onRoot
-import com.android.compose.theme.PlatformTheme
-import com.android.systemui.testing.screenshot.ScreenshotActivity
-import com.android.systemui.testing.screenshot.SystemUIGoldenImagePathManager
-import com.android.systemui.testing.screenshot.UnitTestBitmapMatcher
-import com.android.systemui.testing.screenshot.drawIntoBitmap
-import org.junit.rules.RuleChain
-import org.junit.rules.TestRule
-import org.junit.runner.Description
-import org.junit.runners.model.Statement
-import platform.test.screenshot.DeviceEmulationRule
-import platform.test.screenshot.DeviceEmulationSpec
-import platform.test.screenshot.MaterialYouColorsRule
-import platform.test.screenshot.ScreenshotTestRule
-import platform.test.screenshot.getEmulatedDevicePathConfig
-
-/** A rule for Compose screenshot diff tests. */
-class ComposeScreenshotTestRule(
-    emulationSpec: DeviceEmulationSpec,
-    assetPathRelativeToBuildRoot: String
-) : TestRule {
-    private val colorsRule = MaterialYouColorsRule()
-    private val deviceEmulationRule = DeviceEmulationRule(emulationSpec)
-    private val screenshotRule =
-        ScreenshotTestRule(
-            SystemUIGoldenImagePathManager(
-                getEmulatedDevicePathConfig(emulationSpec),
-                assetPathRelativeToBuildRoot
-            )
-        )
-    private val composeRule = createAndroidComposeRule<ScreenshotActivity>()
-    private val delegateRule =
-        RuleChain.outerRule(colorsRule)
-            .around(deviceEmulationRule)
-            .around(screenshotRule)
-            .around(composeRule)
-    private val matcher = UnitTestBitmapMatcher
-
-    override fun apply(base: Statement, description: Description): Statement {
-        return delegateRule.apply(base, description)
-    }
-
-    /**
-     * Compare [content] with the golden image identified by [goldenIdentifier] in the context of
-     * [testSpec].
-     */
-    fun screenshotTest(
-        goldenIdentifier: String,
-        content: @Composable () -> Unit,
-    ) {
-        // Make sure that the activity draws full screen and fits the whole display instead of the
-        // system bars.
-        val activity = composeRule.activity
-        activity.mainExecutor.execute { activity.window.setDecorFitsSystemWindows(false) }
-
-        // Set the content using the AndroidComposeRule to make sure that the Activity is set up
-        // correctly.
-        composeRule.setContent {
-            PlatformTheme {
-                Surface(
-                    color = MaterialTheme.colorScheme.background,
-                ) {
-                    content()
-                }
-            }
-        }
-        composeRule.waitForIdle()
-
-        val view = (composeRule.onRoot().fetchSemanticsNode().root as ViewRootForTest).view
-        screenshotRule.assertBitmapAgainstGolden(view.drawIntoBitmap(), goldenIdentifier, matcher)
-    }
-}
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/AnimatableClockView.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/AnimatableClockView.kt
index d208404..b9d6643 100644
--- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/AnimatableClockView.kt
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/AnimatableClockView.kt
@@ -33,8 +33,8 @@
 import com.android.systemui.animation.GlyphCallback
 import com.android.systemui.animation.TextAnimator
 import com.android.systemui.customization.R
-import com.android.systemui.log.LogBuffer
-import com.android.systemui.log.core.LogLevel.DEBUG
+import com.android.systemui.log.core.Logger
+import com.android.systemui.log.core.MessageBuffer
 import java.io.PrintWriter
 import java.util.Calendar
 import java.util.Locale
@@ -51,7 +51,12 @@
     defStyleAttr: Int = 0,
     defStyleRes: Int = 0
 ) : TextView(context, attrs, defStyleAttr, defStyleRes) {
-    var logBuffer: LogBuffer? = null
+    var messageBuffer: MessageBuffer? = null
+        set(value) {
+            logger = if (value != null) Logger(value, TAG) else null
+        }
+
+    private var logger: Logger? = null
 
     private val time = Calendar.getInstance()
 
@@ -129,7 +134,7 @@
 
     override fun onAttachedToWindow() {
         super.onAttachedToWindow()
-        logBuffer?.log(TAG, DEBUG, "onAttachedToWindow")
+        logger?.d("onAttachedToWindow")
         refreshFormat()
     }
 
@@ -145,39 +150,32 @@
         time.timeInMillis = timeOverrideInMillis ?: System.currentTimeMillis()
         contentDescription = DateFormat.format(descFormat, time)
         val formattedText = DateFormat.format(format, time)
-        logBuffer?.log(TAG, DEBUG,
-                { str1 = formattedText?.toString() },
-                { "refreshTime: new formattedText=$str1" }
-        )
+        logger?.d({ "refreshTime: new formattedText=$str1" }) { str1 = formattedText?.toString() }
         // Setting text actually triggers a layout pass (because the text view is set to
         // wrap_content width and TextView always relayouts for this). Avoid needless
         // relayout if the text didn't actually change.
         if (!TextUtils.equals(text, formattedText)) {
             text = formattedText
-            logBuffer?.log(TAG, DEBUG,
-                    { str1 = formattedText?.toString() },
-                    { "refreshTime: done setting new time text to: $str1" }
-            )
+            logger?.d({ "refreshTime: done setting new time text to: $str1" }) {
+                str1 = formattedText?.toString()
+            }
             // Because the TextLayout may mutate under the hood as a result of the new text, we
             // notify the TextAnimator that it may have changed and request a measure/layout. A
             // crash will occur on the next invocation of setTextStyle if the layout is mutated
             // without being notified TextInterpolator being notified.
             if (layout != null) {
                 textAnimator?.updateLayout(layout)
-                logBuffer?.log(TAG, DEBUG, "refreshTime: done updating textAnimator layout")
+                logger?.d("refreshTime: done updating textAnimator layout")
             }
             requestLayout()
-            logBuffer?.log(TAG, DEBUG, "refreshTime: after requestLayout")
+            logger?.d("refreshTime: after requestLayout")
         }
     }
 
     fun onTimeZoneChanged(timeZone: TimeZone?) {
         time.timeZone = timeZone
         refreshFormat()
-        logBuffer?.log(TAG, DEBUG,
-                { str1 = timeZone?.toString() },
-                { "onTimeZoneChanged newTimeZone=$str1" }
-        )
+        logger?.d({ "onTimeZoneChanged newTimeZone=$str1" }) { str1 = timeZone?.toString() }
     }
 
     @SuppressLint("DrawAllocation")
@@ -191,7 +189,7 @@
         } else {
             animator.updateLayout(layout)
         }
-        logBuffer?.log(TAG, DEBUG, "onMeasure")
+        logger?.d("onMeasure")
     }
 
     override fun onDraw(canvas: Canvas) {
@@ -203,12 +201,12 @@
         } else {
             super.onDraw(canvas)
         }
-        logBuffer?.log(TAG, DEBUG, "onDraw")
+        logger?.d("onDraw")
     }
 
     override fun invalidate() {
         super.invalidate()
-        logBuffer?.log(TAG, DEBUG, "invalidate")
+        logger?.d("invalidate")
     }
 
     override fun onTextChanged(
@@ -218,10 +216,7 @@
             lengthAfter: Int
     ) {
         super.onTextChanged(text, start, lengthBefore, lengthAfter)
-        logBuffer?.log(TAG, DEBUG,
-                { str1 = text.toString() },
-                { "onTextChanged text=$str1" }
-        )
+        logger?.d({ "onTextChanged text=$str1" }) { str1 = text.toString() }
     }
 
     fun setLineSpacingScale(scale: Float) {
@@ -235,7 +230,7 @@
     }
 
     fun animateColorChange() {
-        logBuffer?.log(TAG, DEBUG, "animateColorChange")
+        logger?.d("animateColorChange")
         setTextStyle(
             weight = lockScreenWeight,
             textSize = -1f,
@@ -257,7 +252,7 @@
     }
 
     fun animateAppearOnLockscreen() {
-        logBuffer?.log(TAG, DEBUG, "animateAppearOnLockscreen")
+        logger?.d("animateAppearOnLockscreen")
         setTextStyle(
             weight = dozingWeight,
             textSize = -1f,
@@ -283,7 +278,7 @@
         if (isAnimationEnabled && textAnimator == null) {
             return
         }
-        logBuffer?.log(TAG, DEBUG, "animateFoldAppear")
+        logger?.d("animateFoldAppear")
         setTextStyle(
             weight = lockScreenWeightInternal,
             textSize = -1f,
@@ -310,7 +305,7 @@
             // Skip charge animation if dozing animation is already playing.
             return
         }
-        logBuffer?.log(TAG, DEBUG, "animateCharge")
+        logger?.d("animateCharge")
         val startAnimPhase2 = Runnable {
             setTextStyle(
                 weight = if (isDozing()) dozingWeight else lockScreenWeight,
@@ -334,7 +329,7 @@
     }
 
     fun animateDoze(isDozing: Boolean, animate: Boolean) {
-        logBuffer?.log(TAG, DEBUG, "animateDoze")
+        logger?.d("animateDoze")
         setTextStyle(
             weight = if (isDozing) dozingWeight else lockScreenWeight,
             textSize = -1f,
@@ -453,10 +448,7 @@
             isSingleLineInternal && !use24HourFormat -> Patterns.sClockView12
             else -> DOUBLE_LINE_FORMAT_12_HOUR
         }
-        logBuffer?.log(TAG, DEBUG,
-                { str1 = format?.toString() },
-                { "refreshFormat format=$str1" }
-        )
+        logger?.d({ "refreshFormat format=$str1" }) { str1 = format?.toString() }
 
         descFormat = if (use24HourFormat) Patterns.sClockView24 else Patterns.sClockView12
         refreshTime()
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ClockRegistry.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ClockRegistry.kt
index 12f7452..d65edae 100644
--- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ClockRegistry.kt
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ClockRegistry.kt
@@ -23,10 +23,11 @@
 import android.provider.Settings
 import android.util.Log
 import androidx.annotation.OpenForTesting
-import com.android.systemui.log.LogBuffer
 import com.android.systemui.log.LogMessageImpl
 import com.android.systemui.log.core.LogLevel
 import com.android.systemui.log.core.LogMessage
+import com.android.systemui.log.core.Logger
+import com.android.systemui.log.core.MessageBuffer
 import com.android.systemui.log.core.MessageInitializer
 import com.android.systemui.log.core.MessagePrinter
 import com.android.systemui.plugins.ClockController
@@ -75,7 +76,7 @@
 
 private val TMP_MESSAGE: LogMessage by lazy { LogMessageImpl.Factory.create() }
 
-private inline fun LogBuffer?.tryLog(
+private inline fun Logger?.tryLog(
     tag: String,
     level: LogLevel,
     messageInitializer: MessageInitializer,
@@ -84,7 +85,7 @@
 ) {
     if (this != null) {
         // Wrap messagePrinter to convert it from crossinline to noinline
-        this.log(tag, level, messageInitializer, messagePrinter, ex)
+        this.log(level, messagePrinter, ex, messageInitializer)
     } else {
         messageInitializer(TMP_MESSAGE)
         val msg = messagePrinter(TMP_MESSAGE)
@@ -110,7 +111,7 @@
     val handleAllUsers: Boolean,
     defaultClockProvider: ClockProvider,
     val fallbackClockId: ClockId = DEFAULT_CLOCK_ID,
-    val logBuffer: LogBuffer? = null,
+    messageBuffer: MessageBuffer? = null,
     val keepAllLoaded: Boolean,
     subTag: String,
     var isTransitClockEnabled: Boolean = false,
@@ -124,6 +125,7 @@
         fun onAvailableClocksChanged() {}
     }
 
+    private val logger: Logger? = if (messageBuffer != null) Logger(messageBuffer, TAG) else null
     private val availableClocks = ConcurrentHashMap<ClockId, ClockInfo>()
     private val clockChangeListeners = mutableListOf<ClockChangeListener>()
     private val settingObserver =
@@ -150,7 +152,7 @@
 
                 val knownClocks = KNOWN_PLUGINS.get(manager.getPackage())
                 if (knownClocks == null) {
-                    logBuffer.tryLog(
+                    logger.tryLog(
                         TAG,
                         LogLevel.WARNING,
                         { str1 = manager.getPackage() },
@@ -159,7 +161,7 @@
                     return true
                 }
 
-                logBuffer.tryLog(
+                logger.tryLog(
                     TAG,
                     LogLevel.INFO,
                     { str1 = manager.getPackage() },
@@ -176,7 +178,7 @@
                         }
 
                     if (manager != info.manager) {
-                        logBuffer.tryLog(
+                        logger.tryLog(
                             TAG,
                             LogLevel.ERROR,
                             { str1 = id },
@@ -216,7 +218,7 @@
                         }
 
                     if (manager != info.manager) {
-                        logBuffer.tryLog(
+                        logger.tryLog(
                             TAG,
                             LogLevel.ERROR,
                             { str1 = id },
@@ -244,7 +246,7 @@
                     val id = clock.clockId
                     val info = availableClocks[id]
                     if (info?.manager != manager) {
-                        logBuffer.tryLog(
+                        logger.tryLog(
                             TAG,
                             LogLevel.ERROR,
                             { str1 = id },
@@ -319,7 +321,7 @@
 
                 ClockSettings.deserialize(json)
             } catch (ex: Exception) {
-                logBuffer.tryLog(TAG, LogLevel.ERROR, {}, { "Failed to parse clock settings" }, ex)
+                logger.tryLog(TAG, LogLevel.ERROR, {}, { "Failed to parse clock settings" }, ex)
                 null
             }
         settings = result
@@ -348,7 +350,7 @@
                 )
             }
         } catch (ex: Exception) {
-            logBuffer.tryLog(TAG, LogLevel.ERROR, {}, { "Failed to set clock settings" }, ex)
+            logger.tryLog(TAG, LogLevel.ERROR, {}, { "Failed to set clock settings" }, ex)
         }
         settings = value
     }
@@ -508,9 +510,9 @@
     }
 
     private fun onConnected(clockId: ClockId) {
-        logBuffer.tryLog(TAG, LogLevel.DEBUG, { str1 = clockId }, { "Connected $str1" })
+        logger.tryLog(TAG, LogLevel.DEBUG, { str1 = clockId }, { "Connected $str1" })
         if (currentClockId == clockId) {
-            logBuffer.tryLog(
+            logger.tryLog(
                 TAG,
                 LogLevel.INFO,
                 { str1 = clockId },
@@ -520,10 +522,10 @@
     }
 
     private fun onLoaded(clockId: ClockId) {
-        logBuffer.tryLog(TAG, LogLevel.DEBUG, { str1 = clockId }, { "Loaded $str1" })
+        logger.tryLog(TAG, LogLevel.DEBUG, { str1 = clockId }, { "Loaded $str1" })
 
         if (currentClockId == clockId) {
-            logBuffer.tryLog(
+            logger.tryLog(
                 TAG,
                 LogLevel.INFO,
                 { str1 = clockId },
@@ -534,10 +536,10 @@
     }
 
     private fun onUnloaded(clockId: ClockId) {
-        logBuffer.tryLog(TAG, LogLevel.DEBUG, { str1 = clockId }, { "Unloaded $str1" })
+        logger.tryLog(TAG, LogLevel.DEBUG, { str1 = clockId }, { "Unloaded $str1" })
 
         if (currentClockId == clockId) {
-            logBuffer.tryLog(
+            logger.tryLog(
                 TAG,
                 LogLevel.WARNING,
                 { str1 = clockId },
@@ -548,10 +550,10 @@
     }
 
     private fun onDisconnected(clockId: ClockId) {
-        logBuffer.tryLog(TAG, LogLevel.DEBUG, { str1 = clockId }, { "Disconnected $str1" })
+        logger.tryLog(TAG, LogLevel.DEBUG, { str1 = clockId }, { "Disconnected $str1" })
 
         if (currentClockId == clockId) {
-            logBuffer.tryLog(
+            logger.tryLog(
                 TAG,
                 LogLevel.WARNING,
                 { str1 = clockId },
@@ -597,22 +599,17 @@
         if (isEnabled && clockId.isNotEmpty()) {
             val clock = createClock(clockId)
             if (clock != null) {
-                logBuffer.tryLog(
-                    TAG,
-                    LogLevel.INFO,
-                    { str1 = clockId },
-                    { "Rendering clock $str1" }
-                )
+                logger.tryLog(TAG, LogLevel.INFO, { str1 = clockId }, { "Rendering clock $str1" })
                 return clock
             } else if (availableClocks.containsKey(clockId)) {
-                logBuffer.tryLog(
+                logger.tryLog(
                     TAG,
                     LogLevel.WARNING,
                     { str1 = clockId },
                     { "Clock $str1 not loaded; using default" }
                 )
             } else {
-                logBuffer.tryLog(
+                logger.tryLog(
                     TAG,
                     LogLevel.ERROR,
                     { str1 = clockId },
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockController.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockController.kt
index e557c8e..e539c95 100644
--- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockController.kt
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockController.kt
@@ -24,7 +24,7 @@
 import android.widget.FrameLayout
 import androidx.annotation.VisibleForTesting
 import com.android.systemui.customization.R
-import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.core.MessageBuffer
 import com.android.systemui.plugins.ClockAnimations
 import com.android.systemui.plugins.ClockConfig
 import com.android.systemui.plugins.ClockController
@@ -108,10 +108,10 @@
 
         override val config = ClockFaceConfig()
 
-        override var logBuffer: LogBuffer?
-            get() = view.logBuffer
+        override var messageBuffer: MessageBuffer?
+            get() = view.messageBuffer
             set(value) {
-                view.logBuffer = value
+                view.messageBuffer = value
             }
 
         override var animations: DefaultClockAnimations = DefaultClockAnimations(view, 0f, 0f)
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/ClockProviderPlugin.kt b/packages/SystemUI/plugin/src/com/android/systemui/plugins/ClockProviderPlugin.kt
index 537b7a4..d962732 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/ClockProviderPlugin.kt
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/ClockProviderPlugin.kt
@@ -18,7 +18,7 @@
 import android.graphics.drawable.Drawable
 import android.view.View
 import com.android.internal.annotations.Keep
-import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.core.MessageBuffer
 import com.android.systemui.plugins.annotations.ProvidesInterface
 import java.io.PrintWriter
 import java.util.Locale
@@ -95,7 +95,7 @@
     val animations: ClockAnimations
 
     /** Some clocks may log debug information */
-    var logBuffer: LogBuffer?
+    var messageBuffer: MessageBuffer?
 }
 
 /** Events that should call when various rendering parameters change */
diff --git a/packages/SystemUI/proguard_common.flags b/packages/SystemUI/proguard_common.flags
index 1f47e72..9bd26ab1 100644
--- a/packages/SystemUI/proguard_common.flags
+++ b/packages/SystemUI/proguard_common.flags
@@ -65,15 +65,7 @@
 # The plugins, log & common subpackages act as shared libraries that might be referenced in
 # dynamically-loaded plugin APKs.
 -keep class com.android.systemui.plugins.** { *; }
--keep class com.android.systemui.log.ConstantStringsLoggerImpl { *; }
--keep class com.android.systemui.log.ConstantStringsLogger { *; }
--keep class com.android.systemui.log.LogBuffer { *; }
--keep class com.android.systemui.log.LogcatEchoTrackerDebug { *; }
--keep class com.android.systemui.log.LogcatEchoTracker { *; }
--keep class com.android.systemui.log.LogcatEchoTrackerProd { *; }
--keep class com.android.systemui.log.LogLevel { *; }
--keep class com.android.systemui.log.LogMessageImpl { *; }
--keep class com.android.systemui.log.LogMessage { *; }
+-keep class com.android.systemui.log.core.** { *; }
 -keep class com.android.systemui.fragments.FragmentService$FragmentCreator {
     *;
 }
diff --git a/packages/SystemUI/res-keyguard/drawable/ic_lock_locked.xml b/packages/SystemUI/res-keyguard/drawable/ic_lock_locked.xml
new file mode 100644
index 0000000..e572985
--- /dev/null
+++ b/packages/SystemUI/res-keyguard/drawable/ic_lock_locked.xml
@@ -0,0 +1,26 @@
+<?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
+  -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportWidth="24"
+    android:viewportHeight="24">
+  <path
+      android:pathData="M17,8H18C19.1,8 20,8.9 20,10V20C20,21.1 19.1,22 18,22H6C4.9,22 4,21.1 4,20V10C4,8.9 4.9,8 6,8H7V6C7,3.24 9.24,1 12,1C14.76,1 17,3.24 17,6V8ZM12,3C10.34,3 9,4.34 9,6V8H15V6C15,4.34 13.66,3 12,3ZM6,20V10H18V20H6ZM14,15C14,16.1 13.1,17 12,17C10.9,17 10,16.1 10,15C10,13.9 10.9,13 12,13C13.1,13 14,13.9 14,15Z"
+      android:fillColor="#5F6368"
+      android:fillType="evenOdd"/>
+</vector>
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/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/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/screenshot/Android.bp b/packages/SystemUI/screenshot/Android.bp
deleted file mode 100644
index f449398..0000000
--- a/packages/SystemUI/screenshot/Android.bp
+++ /dev/null
@@ -1,45 +0,0 @@
-// Copyright (C) 2022 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//      http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package {
-    // See: http://go/android-license-faq
-    // A large-scale-change added 'default_applicable_licenses' to import
-    // all of the 'license_kinds' from "frameworks_base_packages_SystemUI_license"
-    // to get the below license kinds:
-    //   SPDX-license-identifier-Apache-2.0
-    default_applicable_licenses: ["frameworks_base_packages_SystemUI_license"],
-}
-
-android_library {
-    name: "SystemUIScreenshotLib",
-    manifest: "AndroidManifest.xml",
-
-    srcs: [
-        "src/**/*.kt",
-    ],
-
-    resource_dirs: [
-        "res",
-    ],
-
-    static_libs: [
-        "SystemUI-core",
-        "androidx.test.espresso.core",
-        "androidx.appcompat_appcompat",
-        "platform-screenshot-diff-core",
-        "guava",
-    ],
-
-    kotlincflags: ["-Xjvm-default=all"],
-}
diff --git a/packages/SystemUI/screenshot/AndroidManifest.xml b/packages/SystemUI/screenshot/AndroidManifest.xml
deleted file mode 100644
index ba3dc8c..0000000
--- a/packages/SystemUI/screenshot/AndroidManifest.xml
+++ /dev/null
@@ -1,27 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-     Copyright (C) 2022 The Android Open Source Project
-
-     Licensed under the Apache License, Version 2.0 (the "License");
-     you may not use this file except in compliance with the License.
-     You may obtain a copy of the License at
-
-          http://www.apache.org/licenses/LICENSE-2.0
-
-     Unless required by applicable law or agreed to in writing, software
-     distributed under the License is distributed on an "AS IS" BASIS,
-     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-     See the License for the specific language governing permissions and
-     limitations under the License.
--->
-
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="com.android.systemui.testing.screenshot">
-    <application>
-        <activity
-            android:name="com.android.systemui.testing.screenshot.ScreenshotActivity"
-            android:configChanges="orientation|screenSize|screenLayout|smallestScreenSize"
-            android:exported="true"
-            android:theme="@style/Theme.SystemUI.Screenshot" />
-    </application>
-</manifest>
diff --git a/packages/SystemUI/screenshot/res/values/themes.xml b/packages/SystemUI/screenshot/res/values/themes.xml
deleted file mode 100644
index a7f8a26..0000000
--- a/packages/SystemUI/screenshot/res/values/themes.xml
+++ /dev/null
@@ -1,31 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-     Copyright (C) 2022 The Android Open Source Project
-
-     Licensed under the Apache License, Version 2.0 (the "License");
-     you may not use this file except in compliance with the License.
-     You may obtain a copy of the License at
-
-          http://www.apache.org/licenses/LICENSE-2.0
-
-     Unless required by applicable law or agreed to in writing, software
-     distributed under the License is distributed on an "AS IS" BASIS,
-     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-     See the License for the specific language governing permissions and
-     limitations under the License.
--->
-<resources>
-    <style name="Theme.SystemUI.Screenshot" parent="Theme.SystemUI">
-        <item name="android:windowActionBar">false</item>
-        <item name="android:windowNoTitle">true</item>
-
-        <!-- We make the status and navigation bars transparent so that the screenshotted content is
-             not clipped by the status bar height when drawn into the Bitmap (which is what happens
-             given that we draw the view into the Bitmap using hardware acceleration). -->
-        <item name="android:statusBarColor">@android:color/transparent</item>
-        <item name="android:navigationBarColor">@android:color/transparent</item>
-
-        <!-- Make sure that device specific cutouts don't impact the outcome of screenshot tests -->
-        <item name="android:windowLayoutInDisplayCutoutMode">shortEdges</item>
-    </style>
-</resources>
\ No newline at end of file
diff --git a/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/Bitmap.kt b/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/Bitmap.kt
deleted file mode 100644
index a4a70a4..0000000
--- a/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/Bitmap.kt
+++ /dev/null
@@ -1,66 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.testing.screenshot
-
-import android.graphics.Bitmap
-import android.graphics.Canvas
-import android.os.Build
-import android.view.View
-import platform.test.screenshot.matchers.MSSIMMatcher
-import platform.test.screenshot.matchers.PixelPerfectMatcher
-
-/** Draw this [View] into a [Bitmap]. */
-// TODO(b/195673633): Remove this once Compose screenshot tests use hardware rendering for their
-// tests.
-fun View.drawIntoBitmap(): Bitmap {
-    val bitmap =
-        Bitmap.createBitmap(
-            measuredWidth,
-            measuredHeight,
-            Bitmap.Config.ARGB_8888,
-        )
-    val canvas = Canvas(bitmap)
-    draw(canvas)
-    return bitmap
-}
-
-/**
- * The [BitmapMatcher][platform.test.screenshot.matchers.BitmapMatcher] that should be used for
- * screenshot *unit* tests.
- */
-val UnitTestBitmapMatcher =
-    if (Build.CPU_ABI == "x86_64") {
-        // Different CPU architectures can sometimes end up rendering differently, so we can't do
-        // pixel-perfect matching on different architectures using the same golden. Given that our
-        // presubmits are run on cf_x86_64_phone, our goldens should be perfectly matched on the
-        // x86_64 architecture and use the Structural Similarity Index on others.
-        // TODO(b/237511747): Run our screenshot presubmit tests on arm64 instead so that we can
-        // do pixel perfect matching both at presubmit time and at development time with actual
-        // devices.
-        PixelPerfectMatcher()
-    } else {
-        MSSIMMatcher()
-    }
-
-/**
- * The [BitmapMatcher][platform.test.screenshot.matchers.BitmapMatcher] that should be used for
- * screenshot *unit* tests.
- *
- * We use the Structural Similarity Index for integration tests because they usually contain
- * additional information and noise that shouldn't break the test.
- */
-val IntegrationTestBitmapMatcher = MSSIMMatcher()
diff --git a/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/ExternalViewScreenshotTestRule.kt b/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/ExternalViewScreenshotTestRule.kt
deleted file mode 100644
index e032bb9..0000000
--- a/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/ExternalViewScreenshotTestRule.kt
+++ /dev/null
@@ -1,107 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.testing.screenshot
-
-import android.app.Activity
-import android.graphics.Color
-import android.view.View
-import android.view.Window
-import android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES
-import androidx.core.view.WindowInsetsCompat
-import androidx.core.view.WindowInsetsControllerCompat
-import androidx.core.view.WindowInsetsControllerCompat.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE
-import androidx.test.platform.app.InstrumentationRegistry
-import org.junit.rules.RuleChain
-import org.junit.rules.TestRule
-import org.junit.runner.Description
-import org.junit.runners.model.Statement
-import platform.test.screenshot.*
-
-/**
- * A rule that allows to run a screenshot diff test on a view that is hosted in another activity.
- */
-class ExternalViewScreenshotTestRule(
-    emulationSpec: DeviceEmulationSpec,
-    assetPathRelativeToBuildRoot: String
-) : TestRule {
-
-    private val colorsRule = MaterialYouColorsRule()
-    private val deviceEmulationRule = DeviceEmulationRule(emulationSpec)
-    private val screenshotRule =
-        ScreenshotTestRule(
-            SystemUIGoldenImagePathManager(
-                getEmulatedDevicePathConfig(emulationSpec),
-                assetPathRelativeToBuildRoot
-            )
-        )
-    private val delegateRule =
-        RuleChain.outerRule(colorsRule).around(deviceEmulationRule).around(screenshotRule)
-    private val matcher = UnitTestBitmapMatcher
-
-    override fun apply(base: Statement, description: Description): Statement {
-        return delegateRule.apply(base, description)
-    }
-
-    /**
-     * Compare the content of the [view] with the golden image identified by [goldenIdentifier] in
-     * the context of [emulationSpec]. Window must be specified to capture views that render
-     * hardware buffers.
-     */
-    fun screenshotTest(goldenIdentifier: String, view: View, window: Window? = null) {
-        view.removeElevationRecursively()
-
-        ScreenshotRuleAsserter.Builder(screenshotRule)
-            .setScreenshotProvider { view.toBitmap(window) }
-            .withMatcher(matcher)
-            .build()
-            .assertGoldenImage(goldenIdentifier)
-    }
-
-    /**
-     * Compare the content of the [activity] with the golden image identified by [goldenIdentifier]
-     * in the context of [emulationSpec].
-     */
-    fun activityScreenshotTest(
-        goldenIdentifier: String,
-        activity: Activity,
-    ) {
-        val rootView = activity.window.decorView
-
-        // Hide system bars, remove insets, focus and make sure device-specific cutouts
-        // don't affect screenshots
-        InstrumentationRegistry.getInstrumentation().runOnMainSync {
-            val window = activity.window
-            window.setDecorFitsSystemWindows(false)
-            WindowInsetsControllerCompat(window, rootView).apply {
-                hide(WindowInsetsCompat.Type.systemBars())
-                systemBarsBehavior = BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE
-            }
-
-            window.statusBarColor = Color.TRANSPARENT
-            window.navigationBarColor = Color.TRANSPARENT
-            window.attributes =
-                window.attributes.apply {
-                    layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES
-                }
-
-            rootView.removeInsetsRecursively()
-            activity.currentFocus?.clearFocus()
-        }
-
-        screenshotTest(goldenIdentifier, rootView, activity.window)
-    }
-}
diff --git a/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/SystemUIGoldenImagePathManager.kt b/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/SystemUIGoldenImagePathManager.kt
deleted file mode 100644
index 72d8c5a..0000000
--- a/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/SystemUIGoldenImagePathManager.kt
+++ /dev/null
@@ -1,44 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.testing.screenshot
-
-import androidx.test.platform.app.InstrumentationRegistry
-import platform.test.screenshot.GoldenImagePathManager
-import platform.test.screenshot.PathConfig
-
-/** A [GoldenImagePathManager] that should be used for all SystemUI screenshot tests. */
-class SystemUIGoldenImagePathManager(
-    pathConfig: PathConfig,
-    assetsPathRelativeToBuildRoot: String
-) :
-    GoldenImagePathManager(
-        appContext = InstrumentationRegistry.getInstrumentation().context,
-        assetsPathRelativeToBuildRoot = assetsPathRelativeToBuildRoot,
-        deviceLocalPath =
-            InstrumentationRegistry.getInstrumentation()
-                .targetContext
-                .filesDir
-                .absolutePath
-                .toString() + "/sysui_screenshots",
-        pathConfig = pathConfig,
-    ) {
-    override fun toString(): String {
-        // This string is appended to all actual/expected screenshots on the device, so make sure
-        // it is a static value.
-        return "SystemUIGoldenImagePathManager"
-    }
-}
diff --git a/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/TestAppComponentFactory.kt b/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/TestAppComponentFactory.kt
deleted file mode 100644
index 98e9aaf..0000000
--- a/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/TestAppComponentFactory.kt
+++ /dev/null
@@ -1,60 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.testing.screenshot
-
-import android.app.Activity
-import android.content.Intent
-import androidx.core.app.AppComponentFactory
-
-class TestAppComponentFactory : AppComponentFactory() {
-
-    init {
-        instance = this
-    }
-
-    private val overrides: MutableMap<String, () -> Activity> = hashMapOf()
-
-    fun clearOverrides() {
-        overrides.clear()
-    }
-
-    fun <T : Activity> registerActivityOverride(activity: Class<T>, provider: () -> T) {
-        overrides[activity.name] = provider
-    }
-
-    override fun instantiateActivityCompat(
-        cl: ClassLoader,
-        className: String,
-        intent: Intent?
-    ): Activity {
-        return overrides
-            .getOrDefault(className) { super.instantiateActivityCompat(cl, className, intent) }
-            .invoke()
-    }
-
-    companion object {
-
-        private var instance: TestAppComponentFactory? = null
-
-        fun getInstance(): TestAppComponentFactory =
-            instance
-                ?: error(
-                    "TestAppComponentFactory is not initialized, " +
-                        "did you specify it in the manifest?"
-                )
-    }
-}
diff --git a/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/View.kt b/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/View.kt
deleted file mode 100644
index b84d26a..0000000
--- a/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/View.kt
+++ /dev/null
@@ -1,42 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.testing.screenshot
-
-import android.view.View
-import android.view.ViewGroup
-import com.android.systemui.util.children
-import android.view.WindowInsets
-
-/**
- * Elevation/shadows is not deterministic when doing hardware rendering, this exentsion allows to
- * disable it for any view in the hierarchy.
- */
-fun View.removeElevationRecursively() {
-    this.elevation = 0f
-    (this as? ViewGroup)?.children?.forEach(View::removeElevationRecursively)
-}
-
-/**
- * Different devices could have different insets (e.g. different height of the navigation bar or
- * taskbar). This method dispatches empty insets to the whole view hierarchy and removes
- * the original listener, so the views won't receive real insets.
- */
-fun View.removeInsetsRecursively() {
-    this.dispatchApplyWindowInsets(WindowInsets.CONSUMED)
-    this.setOnApplyWindowInsetsListener(null)
-    (this as? ViewGroup)?.children?.forEach(View::removeInsetsRecursively)
-}
diff --git a/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/ViewCapture.kt b/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/ViewCapture.kt
deleted file mode 100644
index dedf0a7..0000000
--- a/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/ViewCapture.kt
+++ /dev/null
@@ -1,233 +0,0 @@
-package com.android.systemui.testing.screenshot
-
-import android.annotation.WorkerThread
-import android.app.Activity
-import android.content.Context
-import android.content.ContextWrapper
-import android.graphics.Bitmap
-import android.graphics.Canvas
-import android.graphics.HardwareRenderer
-import android.graphics.Rect
-import android.os.Build
-import android.os.Handler
-import android.os.Looper
-import android.util.Log
-import android.view.PixelCopy
-import android.view.SurfaceView
-import android.view.View
-import android.view.ViewTreeObserver
-import android.view.Window
-import androidx.annotation.RequiresApi
-import androidx.concurrent.futures.ResolvableFuture
-import androidx.test.annotation.ExperimentalTestApi
-import androidx.test.core.internal.os.HandlerExecutor
-import androidx.test.espresso.Espresso
-import androidx.test.platform.graphics.HardwareRendererCompat
-import com.google.common.util.concurrent.FutureCallback
-import com.google.common.util.concurrent.Futures
-import com.google.common.util.concurrent.ListenableFuture
-import kotlin.coroutines.suspendCoroutine
-import kotlinx.coroutines.runBlocking
-
-/*
- * This file was forked from androidx/test/core/view/ViewCapture.kt to add [Window] parameter to
- * [View.captureToBitmap].
- * TODO(b/195673633): Remove this fork and use the AndroidX version instead.
- */
-
-/**
- * Asynchronously captures an image of the underlying view into a [Bitmap].
- *
- * For devices below [Build.VERSION_CODES#O] (or if the view's window cannot be determined), the
- * image is obtained using [View#draw]. Otherwise, [PixelCopy] is used.
- *
- * This method will also enable [HardwareRendererCompat#setDrawingEnabled(boolean)] if required.
- *
- * This API is primarily intended for use in lower layer libraries or frameworks. For test authors,
- * its recommended to use espresso or compose's captureToImage.
- *
- * This API is currently experimental and subject to change or removal.
- */
-@ExperimentalTestApi
-@RequiresApi(Build.VERSION_CODES.JELLY_BEAN)
-fun View.captureToBitmap(window: Window? = null): ListenableFuture<Bitmap> {
-    val bitmapFuture: ResolvableFuture<Bitmap> = ResolvableFuture.create()
-    val mainExecutor = HandlerExecutor(Handler(Looper.getMainLooper()))
-    val isRobolectric = if (Build.FINGERPRINT.contains("robolectric")) true else false
-
-    // disable drawing again if necessary once work is complete
-    if (!HardwareRendererCompat.isDrawingEnabled()) {
-        HardwareRendererCompat.setDrawingEnabled(true)
-        bitmapFuture.addListener({ HardwareRendererCompat.setDrawingEnabled(false) }, mainExecutor)
-    }
-
-    mainExecutor.execute {
-        if (isRobolectric) {
-            generateBitmap(bitmapFuture)
-        } else {
-            val forceRedrawFuture = forceRedraw()
-            forceRedrawFuture.addListener({ generateBitmap(bitmapFuture, window) }, mainExecutor)
-        }
-    }
-
-    return bitmapFuture
-}
-
-/**
- * Synchronously captures an image of the view into a [Bitmap]. Synchronous equivalent of
- * [captureToBitmap].
- */
-@WorkerThread
-@ExperimentalTestApi
-@RequiresApi(Build.VERSION_CODES.JELLY_BEAN)
-fun View.toBitmap(window: Window? = null): Bitmap {
-    if (Looper.getMainLooper() == Looper.myLooper()) {
-        error("toBitmap() can't be called from the main thread")
-    }
-
-    if (!HardwareRenderer.isDrawingEnabled()) {
-        error("Hardware rendering is not enabled")
-    }
-
-    // Make sure we are idle.
-    Espresso.onIdle()
-
-    val mainExecutor = context.mainExecutor
-    return runBlocking {
-        suspendCoroutine { continuation ->
-            Futures.addCallback(
-                captureToBitmap(window),
-                object : FutureCallback<Bitmap> {
-                    override fun onSuccess(result: Bitmap?) {
-                        continuation.resumeWith(Result.success(result!!))
-                    }
-
-                    override fun onFailure(t: Throwable) {
-                        continuation.resumeWith(Result.failure(t))
-                    }
-                },
-                // We know that we are not on the main thread, so we can block the current
-                // thread and wait for the result in the main thread.
-                mainExecutor,
-            )
-        }
-    }
-}
-
-/**
- * Trigger a redraw of the given view.
- *
- * Should only be called on UI thread.
- *
- * @return a [ListenableFuture] that will be complete once ui drawing is complete
- */
-// NoClassDefFoundError occurs on API 15
-@RequiresApi(Build.VERSION_CODES.JELLY_BEAN)
-// @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
-@ExperimentalTestApi
-fun View.forceRedraw(): ListenableFuture<Void> {
-    val future: ResolvableFuture<Void> = ResolvableFuture.create()
-
-    if (Build.VERSION.SDK_INT >= 29 && isHardwareAccelerated) {
-        viewTreeObserver.registerFrameCommitCallback() { future.set(null) }
-    } else {
-        viewTreeObserver.addOnDrawListener(
-            object : ViewTreeObserver.OnDrawListener {
-                var handled = false
-                override fun onDraw() {
-                    if (!handled) {
-                        handled = true
-                        future.set(null)
-                        // cannot remove on draw listener inside of onDraw
-                        Handler(Looper.getMainLooper()).post {
-                            viewTreeObserver.removeOnDrawListener(this)
-                        }
-                    }
-                }
-            }
-        )
-    }
-    invalidate()
-    return future
-}
-
-private fun View.generateBitmap(
-    bitmapFuture: ResolvableFuture<Bitmap>,
-    window: Window? = null,
-) {
-    if (bitmapFuture.isCancelled) {
-        return
-    }
-    val destBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888)
-    when {
-        Build.VERSION.SDK_INT < 26 -> generateBitmapFromDraw(destBitmap, bitmapFuture)
-        this is SurfaceView -> generateBitmapFromSurfaceViewPixelCopy(destBitmap, bitmapFuture)
-        else -> {
-            val window = window ?: getActivity()?.window
-            if (window != null) {
-                generateBitmapFromPixelCopy(window, destBitmap, bitmapFuture)
-            } else {
-                Log.i(
-                    "View.captureToImage",
-                    "Could not find window for view. Falling back to View#draw instead of PixelCopy"
-                )
-                generateBitmapFromDraw(destBitmap, bitmapFuture)
-            }
-        }
-    }
-}
-
-@SuppressWarnings("NewApi")
-private fun SurfaceView.generateBitmapFromSurfaceViewPixelCopy(
-    destBitmap: Bitmap,
-    bitmapFuture: ResolvableFuture<Bitmap>
-) {
-    val onCopyFinished =
-        PixelCopy.OnPixelCopyFinishedListener { result ->
-            if (result == PixelCopy.SUCCESS) {
-                bitmapFuture.set(destBitmap)
-            } else {
-                bitmapFuture.setException(
-                    RuntimeException(String.format("PixelCopy failed: %d", result))
-                )
-            }
-        }
-    PixelCopy.request(this, null, destBitmap, onCopyFinished, handler)
-}
-
-internal fun View.generateBitmapFromDraw(
-    destBitmap: Bitmap,
-    bitmapFuture: ResolvableFuture<Bitmap>
-) {
-    destBitmap.density = resources.displayMetrics.densityDpi
-    computeScroll()
-    val canvas = Canvas(destBitmap)
-    canvas.translate((-scrollX).toFloat(), (-scrollY).toFloat())
-    draw(canvas)
-    bitmapFuture.set(destBitmap)
-}
-
-private fun View.getActivity(): Activity? {
-    fun Context.getActivity(): Activity? {
-        return when (this) {
-            is Activity -> this
-            is ContextWrapper -> this.baseContext.getActivity()
-            else -> null
-        }
-    }
-    return context.getActivity()
-}
-
-private fun View.generateBitmapFromPixelCopy(
-    window: Window,
-    destBitmap: Bitmap,
-    bitmapFuture: ResolvableFuture<Bitmap>
-) {
-    val locationInWindow = intArrayOf(0, 0)
-    getLocationInWindow(locationInWindow)
-    val x = locationInWindow[0]
-    val y = locationInWindow[1]
-    val boundsInWindow = Rect(x, y, x + width, y + height)
-
-    return window.generateBitmapFromPixelCopy(boundsInWindow, destBitmap, bitmapFuture)
-}
diff --git a/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/ViewScreenshotTestRule.kt b/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/ViewScreenshotTestRule.kt
deleted file mode 100644
index 070a451..0000000
--- a/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/ViewScreenshotTestRule.kt
+++ /dev/null
@@ -1,188 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.testing.screenshot
-
-import android.app.Activity
-import android.app.Dialog
-import android.graphics.Bitmap
-import android.os.Build
-import android.view.View
-import android.view.ViewGroup
-import android.view.ViewGroup.LayoutParams
-import android.view.ViewGroup.LayoutParams.MATCH_PARENT
-import android.view.ViewGroup.LayoutParams.WRAP_CONTENT
-import androidx.activity.ComponentActivity
-import androidx.test.ext.junit.rules.ActivityScenarioRule
-import java.util.concurrent.TimeUnit
-import org.junit.Assert.assertEquals
-import org.junit.rules.RuleChain
-import org.junit.rules.TestRule
-import org.junit.runner.Description
-import org.junit.runners.model.Statement
-import platform.test.screenshot.DeviceEmulationRule
-import platform.test.screenshot.DeviceEmulationSpec
-import platform.test.screenshot.MaterialYouColorsRule
-import platform.test.screenshot.ScreenshotTestRule
-import platform.test.screenshot.getEmulatedDevicePathConfig
-import platform.test.screenshot.matchers.BitmapMatcher
-
-/** A rule for View screenshot diff unit tests. */
-open class ViewScreenshotTestRule(
-    emulationSpec: DeviceEmulationSpec,
-    private val matcher: BitmapMatcher = UnitTestBitmapMatcher,
-    assetsPathRelativeToBuildRoot: String
-) : TestRule {
-    private val colorsRule = MaterialYouColorsRule()
-    private val deviceEmulationRule = DeviceEmulationRule(emulationSpec)
-    protected val screenshotRule =
-        ScreenshotTestRule(
-            SystemUIGoldenImagePathManager(
-                getEmulatedDevicePathConfig(emulationSpec),
-                assetsPathRelativeToBuildRoot
-            )
-        )
-    private val activityRule = ActivityScenarioRule(ScreenshotActivity::class.java)
-    private val roboRule =
-        RuleChain.outerRule(deviceEmulationRule).around(screenshotRule).around(activityRule)
-    private val delegateRule = RuleChain.outerRule(colorsRule).around(roboRule)
-    private val isRobolectric = if (Build.FINGERPRINT.contains("robolectric")) true else false
-
-    override fun apply(base: Statement, description: Description): Statement {
-        if (isRobolectric) {
-            // In robolectric mode, we enable NATIVE graphics and unpack font and icu files.
-            // We need to use reflection, as this library is only needed and therefore
-            //  only available in deviceless mode.
-            val nativeLoaderClassName = "org.robolectric.nativeruntime.DefaultNativeRuntimeLoader"
-            val defaultNativeRuntimeLoader = Class.forName(nativeLoaderClassName)
-            System.setProperty("robolectric.graphicsMode", "NATIVE")
-            defaultNativeRuntimeLoader.getMethod("injectAndLoad").invoke(null)
-        }
-        val ruleToApply = if (isRobolectric) roboRule else delegateRule
-        return ruleToApply.apply(base, description)
-    }
-
-    protected fun takeScreenshot(
-        mode: Mode = Mode.WrapContent,
-        viewProvider: (ComponentActivity) -> View,
-        beforeScreenshot: (ComponentActivity) -> Unit = {}
-    ): Bitmap {
-        activityRule.scenario.onActivity { activity ->
-            // Make sure that the activity draws full screen and fits the whole display instead of
-            // the system bars.
-            val window = activity.window
-            window.setDecorFitsSystemWindows(false)
-
-            // Set the content.
-            activity.setContentView(viewProvider(activity), mode.layoutParams)
-
-            // Elevation/shadows is not deterministic when doing hardware rendering, so we disable
-            // it for any view in the hierarchy.
-            window.decorView.removeElevationRecursively()
-
-            activity.currentFocus?.clearFocus()
-        }
-
-        // We call onActivity again because it will make sure that our Activity is done measuring,
-        // laying out and drawing its content (that we set in the previous onActivity lambda).
-        var contentView: View? = null
-        activityRule.scenario.onActivity { activity ->
-            // Check that the content is what we expected.
-            val content = activity.requireViewById<ViewGroup>(android.R.id.content)
-            assertEquals(1, content.childCount)
-            contentView = content.getChildAt(0)
-            beforeScreenshot(activity)
-        }
-
-        return if (isRobolectric) {
-            contentView?.captureToBitmap()?.get(10, TimeUnit.SECONDS)
-                ?: error("timeout while trying to capture view to bitmap")
-        } else {
-            contentView?.toBitmap() ?: error("contentView is null")
-        }
-    }
-
-    /**
-     * Compare the content of the view provided by [viewProvider] with the golden image identified
-     * by [goldenIdentifier] in the context of [emulationSpec].
-     */
-    fun screenshotTest(
-        goldenIdentifier: String,
-        mode: Mode = Mode.WrapContent,
-        beforeScreenshot: (ComponentActivity) -> Unit = {},
-        viewProvider: (ComponentActivity) -> View
-    ) {
-        val bitmap = takeScreenshot(mode, viewProvider, beforeScreenshot)
-        screenshotRule.assertBitmapAgainstGolden(
-            bitmap,
-            goldenIdentifier,
-            matcher,
-        )
-    }
-
-    /**
-     * Compare the content of the dialog provided by [dialogProvider] with the golden image
-     * identified by [goldenIdentifier] in the context of [emulationSpec].
-     */
-    fun dialogScreenshotTest(
-        goldenIdentifier: String,
-        dialogProvider: (Activity) -> Dialog,
-    ) {
-        var dialog: Dialog? = null
-        activityRule.scenario.onActivity { activity ->
-            dialog =
-                dialogProvider(activity).apply {
-                    // Make sure that the dialog draws full screen and fits the whole display
-                    // instead of the system bars.
-                    window.setDecorFitsSystemWindows(false)
-
-                    // Disable enter/exit animations.
-                    create()
-                    window.setWindowAnimations(0)
-
-                    // Elevation/shadows is not deterministic when doing hardware rendering, so we
-                    // disable it for any view in the hierarchy.
-                    window.decorView.removeElevationRecursively()
-
-                    // Show the dialog.
-                    show()
-                }
-        }
-
-        try {
-            val bitmap = dialog?.toBitmap() ?: error("dialog is null")
-            screenshotRule.assertBitmapAgainstGolden(
-                bitmap,
-                goldenIdentifier,
-                matcher,
-            )
-        } finally {
-            dialog?.dismiss()
-        }
-    }
-
-    private fun Dialog.toBitmap(): Bitmap {
-        val window = window
-        return window.decorView.toBitmap(window)
-    }
-
-    enum class Mode(val layoutParams: LayoutParams) {
-        WrapContent(LayoutParams(WRAP_CONTENT, WRAP_CONTENT)),
-        MatchSize(LayoutParams(MATCH_PARENT, MATCH_PARENT)),
-        MatchWidth(LayoutParams(MATCH_PARENT, WRAP_CONTENT)),
-        MatchHeight(LayoutParams(WRAP_CONTENT, MATCH_PARENT)),
-    }
-}
diff --git a/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/WindowCapture.kt b/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/WindowCapture.kt
deleted file mode 100644
index d34f46b..0000000
--- a/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/WindowCapture.kt
+++ /dev/null
@@ -1,37 +0,0 @@
-package com.android.systemui.testing.screenshot
-
-import android.graphics.Bitmap
-import android.graphics.Rect
-import android.os.Handler
-import android.os.Looper
-import android.view.PixelCopy
-import android.view.Window
-import androidx.concurrent.futures.ResolvableFuture
-
-/*
- * This file was forked from androidx/test/core/view/WindowCapture.kt.
- * TODO(b/195673633): Remove this fork and use the AndroidX version instead.
- */
-fun Window.generateBitmapFromPixelCopy(
-    boundsInWindow: Rect? = null,
-    destBitmap: Bitmap,
-    bitmapFuture: ResolvableFuture<Bitmap>
-) {
-    val onCopyFinished =
-        PixelCopy.OnPixelCopyFinishedListener { result ->
-            if (result == PixelCopy.SUCCESS) {
-                bitmapFuture.set(destBitmap)
-            } else {
-                bitmapFuture.setException(
-                    RuntimeException(String.format("PixelCopy failed: %d", result))
-                )
-            }
-        }
-    PixelCopy.request(
-        this,
-        boundsInWindow,
-        destBitmap,
-        onCopyFinished,
-        Handler(Looper.getMainLooper())
-    )
-}
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/ClockEventController.kt b/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt
index 91937af..4e0e8d0 100644
--- a/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt
+++ b/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt
@@ -91,9 +91,9 @@
             field = value
             if (value != null) {
                 smallLogBuffer?.log(TAG, DEBUG, {}, { "New Clock" })
-                value.smallClock.logBuffer = smallLogBuffer
+                value.smallClock.messageBuffer = smallLogBuffer
                 largeLogBuffer?.log(TAG, DEBUG, {}, { "New Clock" })
-                value.largeClock.logBuffer = largeLogBuffer
+                value.largeClock.messageBuffer = largeLogBuffer
 
                 value.initialize(resources, dozeAmount, 0f)
 
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPatternView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPatternView.java
index 99b5d52..38c07dc 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPatternView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPatternView.java
@@ -16,8 +16,10 @@
 package com.android.keyguard;
 
 import static com.android.systemui.statusbar.policy.DevicePostureController.DEVICE_POSTURE_HALF_OPENED;
+import static com.android.systemui.statusbar.policy.DevicePostureController.DEVICE_POSTURE_UNKNOWN;
 
 import android.content.Context;
+import android.content.res.Configuration;
 import android.graphics.Rect;
 import android.os.SystemClock;
 import android.text.TextUtils;
@@ -74,6 +76,7 @@
     BouncerKeyguardMessageArea mSecurityMessageDisplay;
     private View mEcaView;
     private ConstraintLayout mContainer;
+    @DevicePostureInt private int mLastDevicePosture = DEVICE_POSTURE_UNKNOWN;
 
     public KeyguardPatternView(Context context) {
         this(context, null);
@@ -95,14 +98,25 @@
                 mContext, android.R.interpolator.fast_out_linear_in));
     }
 
+    @Override
+    protected void onConfigurationChanged(Configuration newConfig) {
+        updateMargins();
+    }
+
     void onDevicePostureChanged(@DevicePostureInt int posture) {
+        mLastDevicePosture = posture;
+        updateMargins();
+    }
+
+    private void updateMargins() {
         // Update the guideline based on the device posture...
         float halfOpenPercentage =
                 mContext.getResources().getFloat(R.dimen.half_opened_bouncer_height_ratio);
 
         ConstraintSet cs = new ConstraintSet();
         cs.clone(mContainer);
-        cs.setGuidelinePercent(R.id.pattern_top_guideline, posture == DEVICE_POSTURE_HALF_OPENED
+        cs.setGuidelinePercent(R.id.pattern_top_guideline,
+                mLastDevicePosture == DEVICE_POSTURE_HALF_OPENED
                 ? halfOpenPercentage : 0.0f);
         cs.applyTo(mContainer);
     }
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPinViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPinViewController.java
index 3e16d55..794e694 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPinViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPinViewController.java
@@ -77,6 +77,7 @@
             });
         }
         mPasswordEntry.setUserActivityListener(this::onUserInput);
+        mView.onDevicePostureChanged(mPostureController.getDevicePosture());
         mPostureController.addCallback(mPostureCallback);
     }
 
diff --git a/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java b/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java
index e255f5c..8ef6c2e 100644
--- a/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java
@@ -56,6 +56,7 @@
 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;
@@ -113,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;
 
@@ -181,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;
@@ -198,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);
@@ -326,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());
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java b/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java
index de7a669..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;
@@ -171,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;
@@ -484,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 =
@@ -505,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
@@ -520,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) {
@@ -531,7 +534,7 @@
                                 final ViewGroup overlayView = mOverlays[i].getRootView();
                                 overlayView.getViewTreeObserver().addOnPreDrawListener(
                                         new RestartingPreDrawListener(
-                                                overlayView, i, newRotation, newDisplayMode));
+                                                overlayView, i, newRotation, newSize));
                             }
                         }
                     }
@@ -541,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;
@@ -943,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) {
@@ -1170,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);
@@ -1494,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;
@@ -1533,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;
@@ -1560,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/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/controls/settings/ControlsSettingsDialogManager.kt b/packages/SystemUI/src/com/android/systemui/controls/settings/ControlsSettingsDialogManager.kt
index ce0f2e9..501bcf0 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/settings/ControlsSettingsDialogManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/settings/ControlsSettingsDialogManager.kt
@@ -135,7 +135,7 @@
         val listener = DialogListener(prefs, attempts, onAttemptCompleted)
         val d =
             dialogProvider(activityContext, R.style.Theme_SystemUI_Dialog).apply {
-                setIcon(R.drawable.ic_warning)
+                setIcon(R.drawable.ic_lock_locked)
                 setOnCancelListener(listener)
                 setNeutralButton(R.string.controls_settings_dialog_neutral_button, listener)
                 setPositiveButton(R.string.controls_settings_dialog_positive_button, listener)
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt b/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt
index 40db63d..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
@@ -111,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
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
index ead24ae..3b89739 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
@@ -59,6 +59,7 @@
 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;
@@ -182,6 +183,7 @@
             LockscreenLayoutModule.class,
             LogModule.class,
             MediaProjectionModule.class,
+            MediaProjectionTaskSwitcherModule.class,
             MotionToolModule.class,
             PeopleHubModule.class,
             PeopleModule.class,
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
index 6abe951..f892a97 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
@@ -89,7 +89,7 @@
     // 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
@@ -146,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.
@@ -429,9 +437,6 @@
     // TODO(b/263272731): Tracking Bug
     val MEDIA_TTT_RECEIVER_SUCCESS_RIPPLE = releasedFlag(910, "media_ttt_receiver_success_ripple")
 
-    // TODO(b/263512203): Tracking Bug
-    val MEDIA_EXPLICIT_INDICATOR = releasedFlag(911, "media_explicit_indicator")
-
     // TODO(b/265813373): Tracking Bug
     val MEDIA_TAP_TO_TRANSFER_DISMISS_GESTURE = releasedFlag(912, "media_ttt_dismiss_gesture")
 
@@ -559,6 +564,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/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/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..576eb9e 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
@@ -716,8 +716,7 @@
         val appUid = currentEntry?.appUid ?: Process.INVALID_UID
         val isExplicit =
             desc.extras?.getLong(MediaConstants.METADATA_KEY_IS_EXPLICIT) ==
-                MediaConstants.METADATA_VALUE_ATTRIBUTE_PRESENT &&
-                mediaFlags.isExplicitIndicatorEnabled()
+                MediaConstants.METADATA_VALUE_ATTRIBUTE_PRESENT
 
         val progress =
             if (mediaFlags.isResumeProgressEnabled()) {
@@ -826,12 +825,10 @@
 
         // Explicit Indicator
         var isExplicit = false
-        if (mediaFlags.isExplicitIndicatorEnabled()) {
-            val mediaMetadataCompat = MediaMetadataCompat.fromMediaMetadata(metadata)
-            isExplicit =
-                mediaMetadataCompat?.getLong(MediaConstants.METADATA_KEY_IS_EXPLICIT) ==
-                    MediaConstants.METADATA_VALUE_ATTRIBUTE_PRESENT
-        }
+        val mediaMetadataCompat = MediaMetadataCompat.fromMediaMetadata(metadata)
+        isExplicit =
+            mediaMetadataCompat?.getLong(MediaConstants.METADATA_KEY_IS_EXPLICIT) ==
+                MediaConstants.METADATA_VALUE_ATTRIBUTE_PRESENT
 
         // Artist name
         var artist: CharSequence? = metadata?.getString(MediaMetadata.METADATA_KEY_ARTIST)
@@ -1253,6 +1250,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/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/controls/util/MediaFlags.kt b/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaFlags.kt
index 9bc66f6..01f047c 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaFlags.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaFlags.kt
@@ -43,9 +43,6 @@
      */
     fun areNearbyMediaDevicesEnabled() = featureFlags.isEnabled(Flags.MEDIA_NEARBY_DEVICES)
 
-    /** Check whether we show explicit indicator on UMO */
-    fun isExplicitIndicatorEnabled() = featureFlags.isEnabled(Flags.MEDIA_EXPLICIT_INDICATOR)
-
     /**
      * If true, keep active media controls for the lifetime of the MediaSession, regardless of
      * whether the underlying notification was dismissed
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/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/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/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/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/phone/CentralSurfacesImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
index 0d3dfae..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;
@@ -1773,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) {
                 }
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/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/tests/src/com/android/keyguard/KeyguardPatternViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPatternViewControllerTest.kt
index 2eea9eb..5d75428 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPatternViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPatternViewControllerTest.kt
@@ -32,15 +32,18 @@
 import com.android.systemui.flags.Flags
 import com.android.systemui.statusbar.policy.DevicePostureController
 import com.android.systemui.statusbar.policy.DevicePostureController.DEVICE_POSTURE_HALF_OPENED
+import com.android.systemui.statusbar.policy.DevicePostureController.DEVICE_POSTURE_OPENED
 import com.android.systemui.util.mockito.any
 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.ArgumentCaptor
 import org.mockito.ArgumentMatchers.anyBoolean
 import org.mockito.ArgumentMatchers.anyInt
 import org.mockito.ArgumentMatchers.anyString
+import org.mockito.Captor
 import org.mockito.Mock
 import org.mockito.Mockito.never
 import org.mockito.Mockito.verify
@@ -78,6 +81,9 @@
   private lateinit var mKeyguardPatternViewController: KeyguardPatternViewController
   private lateinit var fakeFeatureFlags: FakeFeatureFlags
 
+  @Captor
+  lateinit var postureCallbackCaptor: ArgumentCaptor<DevicePostureController.Callback>
+
     @Before
     fun setup() {
         MockitoAnnotations.initMocks(this)
@@ -107,7 +113,7 @@
     }
 
     @Test
-    fun tabletopPostureIsDetectedFromStart() {
+    fun onViewAttached_deviceHalfFolded_propagatedToPatternView() {
         overrideResource(R.dimen.half_opened_bouncer_height_ratio, 0.5f)
         whenever(mPostureController.devicePosture).thenReturn(DEVICE_POSTURE_HALF_OPENED)
 
@@ -116,6 +122,26 @@
         assertThat(getPatternTopGuideline()).isEqualTo(getExpectedTopGuideline())
     }
 
+    @Test
+    fun onDevicePostureChanged_deviceOpened_propagatedToPatternView() {
+        overrideResource(R.dimen.half_opened_bouncer_height_ratio, 0.5f)
+        whenever(mPostureController.devicePosture)
+                .thenReturn(DEVICE_POSTURE_HALF_OPENED)
+
+        mKeyguardPatternViewController.onViewAttached()
+
+        // Verify view begins in posture state DEVICE_POSTURE_HALF_OPENED
+        assertThat(getPatternTopGuideline()).isEqualTo(getExpectedTopGuideline())
+
+        // Simulate posture change to state DEVICE_POSTURE_OPENED with callback
+        verify(mPostureController).addCallback(postureCallbackCaptor.capture())
+        val postureCallback: DevicePostureController.Callback = postureCallbackCaptor.value
+        postureCallback.onPostureChanged(DEVICE_POSTURE_OPENED)
+
+        // Verify view is now in posture state DEVICE_POSTURE_OPENED
+        assertThat(getPatternTopGuideline()).isNotEqualTo(getExpectedTopGuideline())
+    }
+
     private fun getPatternTopGuideline(): Float {
         val cs = ConstraintSet()
         val container =
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinViewControllerTest.kt
index d3b4190..9db267c 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinViewControllerTest.kt
@@ -30,12 +30,16 @@
 import com.android.systemui.flags.FeatureFlags
 import com.android.systemui.flags.Flags
 import com.android.systemui.statusbar.policy.DevicePostureController
+import com.android.systemui.statusbar.policy.DevicePostureController.DEVICE_POSTURE_HALF_OPENED
+import com.android.systemui.statusbar.policy.DevicePostureController.DEVICE_POSTURE_OPENED
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
+import org.mockito.ArgumentCaptor
 import org.mockito.ArgumentMatchers.anyBoolean
 import org.mockito.ArgumentMatchers.anyInt
 import org.mockito.ArgumentMatchers.anyString
+import org.mockito.Captor
 import org.mockito.Mock
 import org.mockito.Mockito
 import org.mockito.Mockito.any
@@ -79,7 +83,9 @@
     @Mock lateinit var deleteButton: NumPadButton
     @Mock lateinit var enterButton: View
 
-    lateinit var pinViewController: KeyguardPinViewController
+    private lateinit var pinViewController: KeyguardPinViewController
+
+    @Captor lateinit var postureCallbackCaptor: ArgumentCaptor<DevicePostureController.Callback>
 
     @Before
     fun setup() {
@@ -97,6 +103,9 @@
         `when`(keyguardPinView.findViewById<NumPadButton>(R.id.delete_button))
             .thenReturn(deleteButton)
         `when`(keyguardPinView.findViewById<View>(R.id.key_enter)).thenReturn(enterButton)
+        // For posture tests:
+        `when`(keyguardPinView.buttons).thenReturn(arrayOf())
+
         pinViewController =
             KeyguardPinViewController(
                 keyguardPinView,
@@ -115,6 +124,33 @@
     }
 
     @Test
+    fun onViewAttached_deviceHalfFolded_propagatedToPinView() {
+        `when`(postureController.devicePosture).thenReturn(DEVICE_POSTURE_HALF_OPENED)
+
+        pinViewController.onViewAttached()
+
+        verify(keyguardPinView).onDevicePostureChanged(DEVICE_POSTURE_HALF_OPENED)
+    }
+
+    @Test
+    fun onDevicePostureChanged_deviceHalfFolded_propagatedToPinView() {
+        `when`(postureController.devicePosture).thenReturn(DEVICE_POSTURE_HALF_OPENED)
+
+        // Verify view begins in posture state DEVICE_POSTURE_HALF_OPENED
+        pinViewController.onViewAttached()
+
+        verify(keyguardPinView).onDevicePostureChanged(DEVICE_POSTURE_HALF_OPENED)
+
+        // Simulate posture change to state DEVICE_POSTURE_OPENED with callback
+        verify(postureController).addCallback(postureCallbackCaptor.capture())
+        val postureCallback: DevicePostureController.Callback = postureCallbackCaptor.value
+        postureCallback.onPostureChanged(DEVICE_POSTURE_OPENED)
+
+        // Verify view is now in posture state DEVICE_POSTURE_OPENED
+        verify(keyguardPinView).onDevicePostureChanged(DEVICE_POSTURE_OPENED)
+    }
+
+    @Test
     fun startAppearAnimation() {
         pinViewController.startAppearAnimation()
         verify(keyguardMessageAreaController)
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/LockIconViewControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/keyguard/LockIconViewControllerBaseTest.java
index e7e1cc9..75106e7 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/LockIconViewControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/LockIconViewControllerBaseTest.java
@@ -42,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;
@@ -93,6 +94,7 @@
     protected @Mock KeyguardTransitionRepository mTransitionRepository;
     protected FakeExecutor mDelayableExecutor = new FakeExecutor(new FakeSystemClock());
     protected FakeFeatureFlags mFeatureFlags;
+    protected @Mock PrimaryBouncerInteractor mPrimaryBouncerInteractor;
 
     protected LockIconViewController mUnderTest;
 
@@ -163,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/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/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/media/controls/pipeline/MediaDataManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDataManagerTest.kt
index 56698e0..d1299d4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDataManagerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDataManagerTest.kt
@@ -261,7 +261,6 @@
         whenever(mediaSmartspaceTarget.creationTimeMillis).thenReturn(SMARTSPACE_CREATION_TIME)
         whenever(mediaSmartspaceTarget.expiryTimeMillis).thenReturn(SMARTSPACE_EXPIRY_TIME)
         whenever(mediaFlags.areMediaSessionActionsEnabled(any(), any())).thenReturn(false)
-        whenever(mediaFlags.isExplicitIndicatorEnabled()).thenReturn(true)
         whenever(mediaFlags.isRetainingPlayersEnabled()).thenReturn(false)
         whenever(mediaFlags.isPersistentSsCardEnabled()).thenReturn(false)
         whenever(mediaFlags.isRemoteResumeAllowed()).thenReturn(false)
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..b4b3073 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
@@ -233,7 +233,6 @@
         FakeFeatureFlags().apply {
             this.set(Flags.UMO_SURFACE_RIPPLE, false)
             this.set(Flags.UMO_TURBULENCE_NOISE, false)
-            this.set(Flags.MEDIA_EXPLICIT_INDICATOR, true)
             this.set(Flags.MEDIA_RECOMMENDATION_CARD_UPDATE, false)
         }
     @Mock private lateinit var globalSettings: GlobalSettings
@@ -1793,7 +1792,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/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/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/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/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/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/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/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/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/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/display/DeviceStateToLayoutMap.java b/services/core/java/com/android/server/display/DeviceStateToLayoutMap.java
index f84a58c..472c1f5 100644
--- a/services/core/java/com/android/server/display/DeviceStateToLayoutMap.java
+++ b/services/core/java/com/android/server/display/DeviceStateToLayoutMap.java
@@ -22,7 +22,6 @@
 import android.util.IndentingPrintWriter;
 import android.util.Slog;
 import android.util.SparseArray;
-import android.view.Display;
 import android.view.DisplayAddress;
 
 import com.android.internal.annotations.VisibleForTesting;
@@ -38,6 +37,7 @@
 import java.io.FileInputStream;
 import java.io.IOException;
 import java.io.InputStream;
+import java.math.BigInteger;
 
 import javax.xml.datatype.DatatypeConfigurationException;
 
@@ -115,13 +115,16 @@
                 Slog.i(TAG, "Display layout config not found: " + configFile);
                 return;
             }
-            int leadDisplayId = Display.DEFAULT_DISPLAY;
             for (com.android.server.display.config.layout.Layout l : layouts.getLayout()) {
                 final int state = l.getState().intValue();
                 final Layout layout = createLayout(state);
                 for (com.android.server.display.config.layout.Display d: l.getDisplay()) {
                     assert layout != null;
                     int position = getPosition(d.getPosition());
+                    BigInteger leadDisplayPhysicalId = d.getLeadDisplayAddress();
+                    DisplayAddress leadDisplayAddress = leadDisplayPhysicalId == null ? null
+                            : DisplayAddress.fromPhysicalDisplayId(
+                                    leadDisplayPhysicalId.longValue());
                     layout.createDisplayLocked(
                             DisplayAddress.fromPhysicalDisplayId(d.getAddress().longValue()),
                             d.isDefaultDisplay(),
@@ -129,11 +132,12 @@
                             d.getDisplayGroup(),
                             mIdProducer,
                             position,
-                            leadDisplayId,
+                            leadDisplayAddress,
                             d.getBrightnessThrottlingMapId(),
                             d.getRefreshRateZoneId(),
                             d.getRefreshRateThermalThrottlingMapId());
                 }
+                layout.postProcessLocked();
             }
         } catch (IOException | DatatypeConfigurationException | XmlPullParserException e) {
             Slog.e(TAG, "Encountered an error while reading/parsing display layout config file: "
diff --git a/services/core/java/com/android/server/display/layout/Layout.java b/services/core/java/com/android/server/display/layout/Layout.java
index b55d7d5..d9ec3de 100644
--- a/services/core/java/com/android/server/display/layout/Layout.java
+++ b/services/core/java/com/android/server/display/layout/Layout.java
@@ -22,6 +22,8 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.text.TextUtils;
+import android.util.ArraySet;
 import android.util.Slog;
 import android.view.DisplayAddress;
 
@@ -77,7 +79,7 @@
             DisplayIdProducer idProducer) {
         createDisplayLocked(address, /* isDefault= */ true, /* isEnabled= */ true,
                 DEFAULT_DISPLAY_GROUP_NAME, idProducer, POSITION_UNKNOWN,
-                NO_LEAD_DISPLAY, /* brightnessThrottlingMapId= */ null,
+                /* leadDisplayAddress= */ null, /* brightnessThrottlingMapId= */ null,
                 /* refreshRateZoneId= */ null, /* refreshRateThermalThrottlingMapId= */ null);
     }
 
@@ -90,19 +92,20 @@
      * @param displayGroupName Name of the display group to which the display is assigned.
      * @param idProducer Produces the logical display id.
      * @param position Indicates the position this display is facing in this layout.
-     * @param leadDisplayId Display that this one follows (-1 if none).
+     * @param leadDisplayAddress Address of a display that this one follows ({@code null} if none).
      * @param brightnessThrottlingMapId Name of which brightness throttling policy should be used.
      * @param refreshRateZoneId Layout limited refresh rate zone name.
      * @param refreshRateThermalThrottlingMapId Name of which refresh rate throttling
      *                                          policy should be used.
-
+     *
      * @exception IllegalArgumentException When a default display owns a display group other than
      *            DEFAULT_DISPLAY_GROUP.
      */
     public void createDisplayLocked(
             @NonNull DisplayAddress address, boolean isDefault, boolean isEnabled,
-            String displayGroupName, DisplayIdProducer idProducer, int position, int leadDisplayId,
-            String brightnessThrottlingMapId, @Nullable String refreshRateZoneId,
+            String displayGroupName, DisplayIdProducer idProducer, int position,
+            @Nullable DisplayAddress leadDisplayAddress, String brightnessThrottlingMapId,
+            @Nullable String refreshRateZoneId,
             @Nullable String refreshRateThermalThrottlingMapId) {
         if (contains(address)) {
             Slog.w(TAG, "Attempting to add second definition for display-device: " + address);
@@ -115,21 +118,27 @@
             return;
         }
 
-        // Assign a logical display ID and create the new display.
-        // Note that the logical display ID is saved into the layout, so when switching between
-        // different layouts, a logical display can be destroyed and later recreated with the
-        // same logical display ID.
         if (displayGroupName == null) {
             displayGroupName = DEFAULT_DISPLAY_GROUP_NAME;
         }
         if (isDefault && !displayGroupName.equals(DEFAULT_DISPLAY_GROUP_NAME)) {
             throw new IllegalArgumentException("Default display should own DEFAULT_DISPLAY_GROUP");
         }
+        if (isDefault && leadDisplayAddress != null) {
+            throw new IllegalArgumentException("Default display cannot have a lead display");
+        }
+        if (address.equals(leadDisplayAddress)) {
+            throw new IllegalArgumentException("Lead display address cannot be the same as display "
+                    + " address");
+        }
+        // Assign a logical display ID and create the new display.
+        // Note that the logical display ID is saved into the layout, so when switching between
+        // different layouts, a logical display can be destroyed and later recreated with the
+        // same logical display ID.
         final int logicalDisplayId = idProducer.getId(isDefault);
-        leadDisplayId = isDefault ? NO_LEAD_DISPLAY : leadDisplayId;
 
         final Display display = new Display(address, logicalDisplayId, isEnabled, displayGroupName,
-                brightnessThrottlingMapId, position, leadDisplayId, refreshRateZoneId,
+                brightnessThrottlingMapId, position, leadDisplayAddress, refreshRateZoneId,
                 refreshRateThermalThrottlingMapId);
 
         mDisplays.add(display);
@@ -146,6 +155,43 @@
     }
 
     /**
+     * Applies post-processing to displays to make sure the information of each display is
+     * up-to-date.
+     *
+     * <p>At creation of a display, lead display is specified by display address. At post
+     * processing, we convert it to logical display ID.
+     */
+    public void postProcessLocked() {
+        for (int i = 0; i < mDisplays.size(); i++) {
+            Display display = mDisplays.get(i);
+            if (display.getLogicalDisplayId() == DEFAULT_DISPLAY) {
+                display.setLeadDisplayId(NO_LEAD_DISPLAY);
+                continue;
+            }
+            DisplayAddress leadDisplayAddress = display.getLeadDisplayAddress();
+            if (leadDisplayAddress == null) {
+                display.setLeadDisplayId(NO_LEAD_DISPLAY);
+                continue;
+            }
+            Display leadDisplay = getByAddress(leadDisplayAddress);
+            if (leadDisplay == null) {
+                throw new IllegalArgumentException("Cannot find a lead display whose address is "
+                        + leadDisplayAddress);
+            }
+            if (!TextUtils.equals(display.getDisplayGroupName(),
+                    leadDisplay.getDisplayGroupName())) {
+                throw new IllegalArgumentException("Lead display(" + leadDisplay + ") should be in "
+                        + "the same display group of the display(" + display + ")");
+            }
+            if (hasCyclicLeadDisplay(display)) {
+                throw new IllegalArgumentException("Display(" + display + ") has a cyclic lead "
+                        + "display");
+            }
+            display.setLeadDisplayId(leadDisplay.getLogicalDisplayId());
+        }
+    }
+
+    /**
      * @param address The address to check.
      *
      * @return True if the specified address is used in this layout.
@@ -208,6 +254,20 @@
         return mDisplays.size();
     }
 
+    private boolean hasCyclicLeadDisplay(Display display) {
+        ArraySet<Display> visited = new ArraySet<>();
+
+        while (display != null) {
+            if (visited.contains(display)) {
+                return true;
+            }
+            visited.add(display);
+            DisplayAddress leadDisplayAddress = display.getLeadDisplayAddress();
+            display = leadDisplayAddress == null ? null : getByAddress(leadDisplayAddress);
+        }
+        return false;
+    }
+
     /**
      * Describes how a {@link LogicalDisplay} is built from {@link DisplayDevice}s.
      */
@@ -240,8 +300,9 @@
         @Nullable
         private final String mThermalBrightnessThrottlingMapId;
 
-        // The ID of the lead display that this display will follow in a layout. -1 means no lead.
-        private final int mLeadDisplayId;
+        // The address of the lead display that is specified in display-layout-configuration.
+        @Nullable
+        private final DisplayAddress mLeadDisplayAddress;
 
         // Refresh rate zone id for specific layout
         @Nullable
@@ -250,9 +311,13 @@
         @Nullable
         private final String mThermalRefreshRateThrottlingMapId;
 
+        // The ID of the lead display that this display will follow in a layout. -1 means no lead.
+        // This is determined using {@code mLeadDisplayAddress}.
+        private int mLeadDisplayId;
+
         private Display(@NonNull DisplayAddress address, int logicalDisplayId, boolean isEnabled,
                 @NonNull String displayGroupName, String brightnessThrottlingMapId, int position,
-                int leadDisplayId, @Nullable String refreshRateZoneId,
+                @Nullable DisplayAddress leadDisplayAddress, @Nullable String refreshRateZoneId,
                 @Nullable String refreshRateThermalThrottlingMapId) {
             mAddress = address;
             mLogicalDisplayId = logicalDisplayId;
@@ -260,9 +325,10 @@
             mDisplayGroupName = displayGroupName;
             mPosition = position;
             mThermalBrightnessThrottlingMapId = brightnessThrottlingMapId;
+            mLeadDisplayAddress = leadDisplayAddress;
             mRefreshRateZoneId = refreshRateZoneId;
             mThermalRefreshRateThrottlingMapId = refreshRateThermalThrottlingMapId;
-            mLeadDisplayId = leadDisplayId;
+            mLeadDisplayId = NO_LEAD_DISPLAY;
         }
 
         @Override
@@ -276,6 +342,7 @@
                     + ", mThermalBrightnessThrottlingMapId: " + mThermalBrightnessThrottlingMapId
                     + ", mRefreshRateZoneId: " + mRefreshRateZoneId
                     + ", mLeadDisplayId: " + mLeadDisplayId
+                    + ", mLeadDisplayAddress: " + mLeadDisplayAddress
                     + ", mThermalRefreshRateThrottlingMapId: " + mThermalRefreshRateThrottlingMapId
                     + "}";
         }
@@ -297,6 +364,7 @@
                     otherDisplay.mThermalBrightnessThrottlingMapId)
                     && Objects.equals(otherDisplay.mRefreshRateZoneId, this.mRefreshRateZoneId)
                     && this.mLeadDisplayId == otherDisplay.mLeadDisplayId
+                    && Objects.equals(mLeadDisplayAddress, otherDisplay.mLeadDisplayAddress)
                     && Objects.equals(mThermalRefreshRateThrottlingMapId,
                     otherDisplay.mThermalRefreshRateThrottlingMapId);
         }
@@ -309,9 +377,10 @@
             result = 31 * result + mLogicalDisplayId;
             result = 31 * result + mDisplayGroupName.hashCode();
             result = 31 * result + mAddress.hashCode();
-            result = 31 * result + mThermalBrightnessThrottlingMapId.hashCode();
+            result = 31 * result + Objects.hashCode(mThermalBrightnessThrottlingMapId);
             result = 31 * result + Objects.hashCode(mRefreshRateZoneId);
             result = 31 * result + mLeadDisplayId;
+            result = 31 * result + Objects.hashCode(mLeadDisplayAddress);
             result = 31 * result + Objects.hashCode(mThermalRefreshRateThrottlingMapId);
             return result;
         }
@@ -360,8 +429,20 @@
             return mLeadDisplayId;
         }
 
+        /**
+         * @return Display address of the display that this one follows.
+         */
+        @Nullable
+        public DisplayAddress getLeadDisplayAddress() {
+            return mLeadDisplayAddress;
+        }
+
         public String getRefreshRateThermalThrottlingMapId() {
             return mThermalRefreshRateThrottlingMapId;
         }
+
+        private void setLeadDisplayId(int id) {
+            mLeadDisplayId = id;
+        }
     }
 }
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 1ab83f7..02ee96a 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -2472,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),
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 68f49fd..d093393 100644
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -4443,7 +4443,7 @@
                     // Remove background token before returning notification to untrusted app, this
                     // ensures the app isn't able to perform background operations that are
                     // associated with notification interactions.
-                    notification.setAllowlistToken(null);
+                    notification.clearAllowlistToken();
                     return new StatusBarNotification(
                             sbn.getPackageName(),
                             sbn.getOpPkg(),
@@ -9508,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;
+        }
     }
 
     /**
@@ -9524,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());
     }
 
@@ -9593,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
@@ -9831,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);
             }
@@ -9873,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;
             }
@@ -9888,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/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index 9402fca..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;
@@ -1817,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;
@@ -1838,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.
@@ -1855,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 =
@@ -1873,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;
                     }
                 }
             }
@@ -1903,7 +1906,7 @@
                             event.getEventTime()));
                 }
             }
-            return -1;
+            return true;
         }
 
         private void handleDoubleTapOnHome() {
@@ -2949,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) {
@@ -2977,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;
         }
@@ -2992,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:
@@ -3045,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:
@@ -3129,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) {
@@ -3169,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:
@@ -3190,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
@@ -3199,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;
                         }
                     }
                 }
@@ -3230,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:
@@ -3250,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:
@@ -3289,7 +3328,7 @@
                         mPendingMetaAction = false;
                     }
                 }
-                return key_consumed;
+                return true;
             case KeyEvent.KEYCODE_ALT_LEFT:
             case KeyEvent.KEYCODE_ALT_RIGHT:
                 if (down) {
@@ -3305,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;
@@ -3322,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
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/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java
index 98c15dd2..2717a6a 100644
--- a/services/core/java/com/android/server/wm/DisplayPolicy.java
+++ b/services/core/java/com/android/server/wm/DisplayPolicy.java
@@ -1284,6 +1284,12 @@
         return ANIMATION_STYLEABLE;
     }
 
+    // TODO (b/277891341): Remove this and related usages. This has been replaced by
+    //                     InsetsSource#FLAG_FORCE_CONSUMING.
+    public boolean areSystemBarsForcedConsumedLw() {
+        return false;
+    }
+
     /**
      * Computes the frames of display (its logical size, rotation and cutout should already be set)
      * used to layout window. This method only changes the given display frames, insets state and
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 4620266..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,15 +48,15 @@
 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.
@@ -79,48 +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 @InsetsType int mForcedShowingTypes;
-    private boolean mAnimatingShown;
 
     private final boolean mHideNavBarForKeyboard;
-    private final float[] mTmpFloat9 = new float[9];
 
     InsetsPolicy(InsetsStateController stateController, DisplayContent displayContent) {
         mStateController = stateController;
@@ -128,6 +91,10 @@
         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. */
@@ -144,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 */)
@@ -200,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;
@@ -223,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) {
@@ -502,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) {
@@ -519,13 +471,13 @@
         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())
@@ -556,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.
@@ -579,13 +531,13 @@
         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)
@@ -662,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,
@@ -760,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/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/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 1b27bb1..db3b267 100644
--- a/services/core/java/com/android/server/wm/Transition.java
+++ b/services/core/java/com/android/server/wm/Transition.java
@@ -619,6 +619,12 @@
         if (!isInTransientHide(wc)) {
             mSyncEngine.addToSyncSet(mSyncId, wc);
         }
+        if (wc.asWindowToken() != null && wc.asWindowToken().mRoundedCornerOverlay) {
+            // Only need to sync the transaction (SyncSet) without ChangeInfo because cutout and
+            // rounded corner overlay never need animations. Especially their surfaces may be put
+            // in root (null, see WindowToken#makeSurface()) that cannot reparent.
+            return;
+        }
         ChangeInfo info = mChanges.get(wc);
         if (info == null) {
             info = new ChangeInfo(wc);
@@ -2270,7 +2276,7 @@
             // transitions anyways).
             return wc.getParent().asDisplayContent().getWindowingLayer();
         }
-        return wc.getParentSurfaceControl();
+        return wc.getParent().getSurfaceControl();
     }
 
     /**
diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java
index 4bc4c26..457a555 100644
--- a/services/core/java/com/android/server/wm/WindowContainer.java
+++ b/services/core/java/com/android/server/wm/WindowContainer.java
@@ -81,7 +81,9 @@
 import android.graphics.Rect;
 import android.os.Debug;
 import android.os.IBinder;
+import android.os.RemoteException;
 import android.os.Trace;
+import android.util.ArrayMap;
 import android.util.ArraySet;
 import android.util.Pair;
 import android.util.Pools;
@@ -174,6 +176,9 @@
      */
     protected SparseArray<InsetsSourceProvider> mInsetsSourceProviders = null;
 
+    @Nullable
+    private ArrayMap<IBinder, DeathRecipient> mInsetsOwnerDeathRecipientMap;
+
     // List of children for this window container. List is in z-order as the children appear on
     // screen with the top-most window container at the tail of the list.
     protected final WindowList<E> mChildren = new WindowList<E>();
@@ -419,11 +424,12 @@
      * Adds an {@link InsetsFrameProvider} which describes what insets should be provided to
      * this {@link WindowContainer} and its children.
      *
-     * @param provider describes the insets types and the frames.
+     * @param provider describes the insets type and the frame.
+     * @param owner owns the insets source which only exists when the owner is alive.
      */
-    void addLocalInsetsFrameProvider(InsetsFrameProvider provider) {
-        if (provider == null) {
-            throw new IllegalArgumentException("Insets type not specified.");
+    void addLocalInsetsFrameProvider(InsetsFrameProvider provider, IBinder owner) {
+        if (provider == null || owner == null) {
+            throw new IllegalArgumentException("Insets provider or owner not specified.");
         }
         if (mDisplayContent == null) {
             // This is possible this container is detached when WM shell is responding to a previous
@@ -432,10 +438,26 @@
             Slog.w(TAG, "Can't add insets frame provider when detached. " + this);
             return;
         }
+
+        if (mInsetsOwnerDeathRecipientMap == null) {
+            mInsetsOwnerDeathRecipientMap = new ArrayMap<>();
+        }
+        DeathRecipient deathRecipient = mInsetsOwnerDeathRecipientMap.get(owner);
+        if (deathRecipient == null) {
+            deathRecipient = new DeathRecipient(owner);
+            try {
+                owner.linkToDeath(deathRecipient, 0);
+            } catch (RemoteException e) {
+                Slog.w(TAG, "Failed to add source for " + provider + " since the owner has died.");
+                return;
+            }
+            mInsetsOwnerDeathRecipientMap.put(owner, deathRecipient);
+        }
+        final int id = provider.getId();
+        deathRecipient.addSourceId(id);
         if (mLocalInsetsSources == null) {
             mLocalInsetsSources = new SparseArray<>();
         }
-        final int id = provider.getId();
         if (mLocalInsetsSources.get(id) != null) {
             if (DEBUG) {
                 Slog.d(TAG, "The local insets source for this " + provider
@@ -448,27 +470,77 @@
         mDisplayContent.getInsetsStateController().updateAboveInsetsState(true);
     }
 
-    void removeLocalInsetsFrameProvider(InsetsFrameProvider provider) {
-        if (provider == null) {
-            throw new IllegalArgumentException("Insets type not specified.");
-        }
-        if (mLocalInsetsSources == null) {
-            return;
+    private class DeathRecipient implements IBinder.DeathRecipient {
+
+        private final IBinder mOwner;
+        private final ArraySet<Integer> mSourceIds = new ArraySet<>();
+
+        DeathRecipient(IBinder owner) {
+            mOwner = owner;
         }
 
-        final int id = provider.getId();
-        if (mLocalInsetsSources.get(id) == null) {
-            if (DEBUG) {
-                Slog.d(TAG, "Given " + provider + " doesn't have a local insets source.");
+        void addSourceId(int id) {
+            mSourceIds.add(id);
+        }
+
+        void removeSourceId(int id) {
+            mSourceIds.remove(id);
+        }
+
+        boolean hasSource() {
+            return !mSourceIds.isEmpty();
+        }
+
+        @Override
+        public void binderDied() {
+            synchronized (mWmService.mGlobalLock) {
+                boolean changed = false;
+                for (int i = mSourceIds.size() - 1; i >= 0; i--) {
+                    changed |= removeLocalInsetsSource(mSourceIds.valueAt(i));
+                }
+                mSourceIds.clear();
+                mOwner.unlinkToDeath(this, 0);
+                mInsetsOwnerDeathRecipientMap.remove(mOwner);
+                if (changed && mDisplayContent != null) {
+                    mDisplayContent.getInsetsStateController().updateAboveInsetsState(true);
+                }
             }
-            return;
         }
-        mLocalInsetsSources.remove(id);
+    }
 
-        // Update insets if this window is attached.
-        if (mDisplayContent != null) {
+    void removeLocalInsetsFrameProvider(InsetsFrameProvider provider, IBinder owner) {
+        if (provider == null || owner == null) {
+            throw new IllegalArgumentException("Insets provider or owner not specified.");
+        }
+        final int id = provider.getId();
+        if (removeLocalInsetsSource(id) && mDisplayContent != null) {
             mDisplayContent.getInsetsStateController().updateAboveInsetsState(true);
         }
+        if (mInsetsOwnerDeathRecipientMap == null) {
+            return;
+        }
+        final DeathRecipient deathRecipient = mInsetsOwnerDeathRecipientMap.get(owner);
+        if (deathRecipient == null) {
+            return;
+        }
+        deathRecipient.removeSourceId(id);
+        if (!deathRecipient.hasSource()) {
+            owner.unlinkToDeath(deathRecipient, 0);
+            mInsetsOwnerDeathRecipientMap.remove(owner);
+        }
+    }
+
+    private boolean removeLocalInsetsSource(int id) {
+        if (mLocalInsetsSources == null) {
+            return false;
+        }
+        if (mLocalInsetsSources.removeReturnOld(id) == null) {
+            if (DEBUG) {
+                Slog.d(TAG, "Given id " + Integer.toHexString(id) + " doesn't exist.");
+            }
+            return false;
+        }
+        return true;
     }
 
     /**
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 68a6384..0e19671 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -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,
@@ -1789,6 +1765,9 @@
             winAnimator.mEnterAnimationPending = true;
             winAnimator.mEnteringAnimation = true;
 
+            if (displayPolicy.areSystemBarsForcedConsumedLw()) {
+                res |= WindowManagerGlobal.ADD_FLAG_ALWAYS_CONSUME_SYSTEM_BARS;
+            }
             if (displayContent.isInTouchMode()) {
                 res |= WindowManagerGlobal.ADD_FLAG_IN_TOUCH_MODE;
             }
@@ -2491,6 +2470,9 @@
             if (win.mActivityRecord != null) {
                 win.mActivityRecord.updateReportedVisibilityLocked();
             }
+            if (displayPolicy.areSystemBarsForcedConsumedLw()) {
+                result |= WindowManagerGlobal.RELAYOUT_RES_CONSUME_ALWAYS_SYSTEM_BARS;
+            }
             if (!win.isGoneForLayout()) {
                 win.mResizedWhileGone = false;
             }
@@ -9064,7 +9046,7 @@
     }
 
     @Override
-    public void getWindowInsets(int displayId, IBinder token, InsetsState outInsetsState) {
+    public boolean getWindowInsets(int displayId, IBinder token, InsetsState outInsetsState) {
         final long origId = Binder.clearCallingIdentity();
         try {
             synchronized (mGlobalLock) {
@@ -9075,6 +9057,7 @@
                 }
                 final WindowToken winToken = dc.getWindowToken(token);
                 dc.getInsetsPolicy().getInsetsForWindowMetrics(winToken, outInsetsState);
+                return dc.getDisplayPolicy().areSystemBarsForcedConsumedLw();
             }
         } finally {
             Binder.restoreCallingIdentity(origId);
diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java
index 31918f4..d1b00a3 100644
--- a/services/core/java/com/android/server/wm/WindowOrganizerController.java
+++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java
@@ -1090,7 +1090,8 @@
                             + container);
                     break;
                 }
-                container.addLocalInsetsFrameProvider(hop.getInsetsFrameProvider());
+                container.addLocalInsetsFrameProvider(
+                        hop.getInsetsFrameProvider(), hop.getInsetsFrameOwner());
                 break;
             }
             case HIERARCHY_OP_TYPE_REMOVE_INSETS_FRAME_PROVIDER: {
@@ -1100,7 +1101,8 @@
                                     + container);
                     break;
                 }
-                container.removeLocalInsetsFrameProvider(hop.getInsetsFrameProvider());
+                container.removeLocalInsetsFrameProvider(
+                        hop.getInsetsFrameProvider(), hop.getInsetsFrameOwner());
                 break;
             }
             case HIERARCHY_OP_TYPE_SET_ALWAYS_ON_TOP: {
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index ef447020..0d4c2d6 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -3732,6 +3732,8 @@
         final boolean isDragResizeChanged = isDragResizeChanged();
         final boolean forceRelayout = syncWithBuffers || isDragResizeChanged;
         final DisplayContent displayContent = getDisplayContent();
+        final boolean alwaysConsumeSystemBars =
+                displayContent.getDisplayPolicy().areSystemBarsForcedConsumedLw();
         final int displayId = displayContent.getDisplayId();
 
         if (isDragResizeChanged) {
@@ -3743,7 +3745,7 @@
 
         try {
             mClient.resized(mClientWindowFrames, reportDraw, mLastReportedConfiguration,
-                    getCompatInsetsState(), forceRelayout, displayId,
+                    getCompatInsetsState(), forceRelayout, alwaysConsumeSystemBars, displayId,
                     syncWithBuffers ? mSyncSeqId : -1, isDragResizing);
             if (drawPending && prevRotation >= 0 && prevRotation != mLastReportedConfiguration
                     .getMergedConfiguration().windowConfiguration.getRotation()) {
diff --git a/services/core/java/com/android/server/wm/WindowToken.java b/services/core/java/com/android/server/wm/WindowToken.java
index 9806be8..31afcbf 100644
--- a/services/core/java/com/android/server/wm/WindowToken.java
+++ b/services/core/java/com/android/server/wm/WindowToken.java
@@ -388,23 +388,12 @@
     @Override
     SurfaceControl.Builder makeSurface() {
         final SurfaceControl.Builder builder = super.makeSurface();
-        // The overlay may use COLOR_MODE_A8 that needs to be at the top of the display to avoid
-        // additional memory usage, see b/235601833. Note that getParentSurfaceControl() must use
-        // the same parent.
         if (mRoundedCornerOverlay) {
             builder.setParent(null);
         }
         return builder;
     }
 
-    @Override
-    public SurfaceControl getParentSurfaceControl() {
-        if (mRoundedCornerOverlay) {
-            return null;
-        }
-        return super.getParentSurfaceControl();
-    }
-
     boolean isClientVisible() {
         return mClientVisible;
     }
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/core/xsd/display-layout-config/display-layout-config.xsd b/services/core/xsd/display-layout-config/display-layout-config.xsd
index ce022e9..57b5d00 100644
--- a/services/core/xsd/display-layout-config/display-layout-config.xsd
+++ b/services/core/xsd/display-layout-config/display-layout-config.xsd
@@ -53,6 +53,7 @@
             <xs:element name="position" type="xs:string" minOccurs="0" maxOccurs="1" />
             <xs:element name="brightnessThrottlingMapId" type="xs:string" minOccurs="0" maxOccurs="1" />
             <xs:element name="refreshRateThermalThrottlingMapId" type="xs:string" minOccurs="0" />
+            <xs:element name="leadDisplayAddress" type="xs:nonNegativeInteger" minOccurs="0" maxOccurs="1" />
         </xs:sequence>
         <xs:attribute name="enabled" type="xs:boolean" use="optional" />
         <xs:attribute name="defaultDisplay" type="xs:boolean" use="optional" />
diff --git a/services/core/xsd/display-layout-config/schema/current.txt b/services/core/xsd/display-layout-config/schema/current.txt
index 42a800d..2d4f7a4 100644
--- a/services/core/xsd/display-layout-config/schema/current.txt
+++ b/services/core/xsd/display-layout-config/schema/current.txt
@@ -6,6 +6,7 @@
     method public java.math.BigInteger getAddress();
     method public String getBrightnessThrottlingMapId();
     method public String getDisplayGroup();
+    method public java.math.BigInteger getLeadDisplayAddress();
     method public String getPosition();
     method public String getRefreshRateThermalThrottlingMapId();
     method public String getRefreshRateZoneId();
@@ -16,6 +17,7 @@
     method public void setDefaultDisplay(boolean);
     method public void setDisplayGroup(String);
     method public void setEnabled(boolean);
+    method public void setLeadDisplayAddress(java.math.BigInteger);
     method public void setPosition(String);
     method public void setRefreshRateThermalThrottlingMapId(String);
     method public void setRefreshRateZoneId(String);
diff --git a/services/tests/displayservicetests/src/com/android/server/display/DeviceStateToLayoutMapTest.java b/services/tests/displayservicetests/src/com/android/server/display/DeviceStateToLayoutMapTest.java
index 130e6ad..4b124ca 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/DeviceStateToLayoutMapTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/DeviceStateToLayoutMapTest.java
@@ -19,8 +19,8 @@
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertThrows;
 
-import android.view.Display;
 import android.view.DisplayAddress;
 
 import androidx.test.filters.SmallTest;
@@ -65,9 +65,15 @@
 
         Layout testLayout = new Layout();
         createDefaultDisplay(testLayout, 123456L);
-        createNonDefaultDisplay(testLayout, 78910L, /* enabled= */ false, /* group= */ null);
-        createNonDefaultDisplay(testLayout, 98765L, /* enabled= */ true, /* group= */ "group1");
-        createNonDefaultDisplay(testLayout, 786L, /* enabled= */ false, /* group= */ "group2");
+        createNonDefaultDisplay(testLayout, 78910L, /* enabled= */ false, /* group= */ null,
+                /* leadDisplayAddress= */ null);
+        createNonDefaultDisplay(testLayout, 98765L, /* enabled= */ true, /* group= */ "group1",
+                /* leadDisplayAddress= */ null);
+        createNonDefaultDisplay(testLayout, 786L, /* enabled= */ false, /* group= */ "group2",
+                /* leadDisplayAddress= */ null);
+        createNonDefaultDisplay(testLayout, 1092L, /* enabled= */ true, /* group= */ null,
+                DisplayAddress.fromPhysicalDisplayId(78910L));
+        testLayout.postProcessLocked();
 
         assertEquals(testLayout, configLayout);
     }
@@ -78,7 +84,9 @@
 
         Layout testLayout = new Layout();
         createDefaultDisplay(testLayout, 78910L);
-        createNonDefaultDisplay(testLayout, 123456L, /* enabled= */ false, /* group= */ null);
+        createNonDefaultDisplay(testLayout, 123456L, /* enabled= */ false, /* group= */ null,
+                /* leadDisplayAddress= */ null);
+        testLayout.postProcessLocked();
 
         assertEquals(testLayout, configLayout);
     }
@@ -122,20 +130,132 @@
         Layout testLayout = new Layout();
         testLayout.createDisplayLocked(DisplayAddress.fromPhysicalDisplayId(345L),
                 /* isDefault= */ true, /* isEnabled= */ true, /* displayGroupName= */ null,
-                mDisplayIdProducerMock,  Layout.Display.POSITION_FRONT, Display.DEFAULT_DISPLAY,
-                /* brightnessThrottlingMapId= */ "brightness1",
+                mDisplayIdProducerMock,  Layout.Display.POSITION_FRONT,
+                /* leadDisplayAddress= */ null, /* brightnessThrottlingMapId= */ "brightness1",
                 /* refreshRateZoneId= */ "zone1",
                 /* refreshRateThermalThrottlingMapId= */ "rr1");
         testLayout.createDisplayLocked(DisplayAddress.fromPhysicalDisplayId(678L),
                 /* isDefault= */ false, /* isEnabled= */ false, /* displayGroupName= */ "group1",
-                mDisplayIdProducerMock, Layout.Display.POSITION_REAR, Display.DEFAULT_DISPLAY,
-                /* brightnessThrottlingMapId= */ "brightness2",
+                mDisplayIdProducerMock, Layout.Display.POSITION_REAR,
+                /* leadDisplayAddress= */ null, /* brightnessThrottlingMapId= */ "brightness2",
                 /* refreshRateZoneId= */ "zone2",
                 /* refreshRateThermalThrottlingMapId= */ "rr2");
+        testLayout.postProcessLocked();
 
         assertEquals(testLayout, configLayout);
     }
 
+    @Test
+    public void testLeadDisplayAddress() {
+        Layout layout = new Layout();
+        createNonDefaultDisplay(layout, 111L, /* enabled= */ true, /* group= */ null,
+                /* leadDisplayAddress= */ null);
+        createNonDefaultDisplay(layout, 222L, /* enabled= */ true, /* group= */ null,
+                DisplayAddress.fromPhysicalDisplayId(111L));
+
+        layout.postProcessLocked();
+
+        com.android.server.display.layout.Layout.Display display111 =
+                layout.getByAddress(DisplayAddress.fromPhysicalDisplayId(111));
+        com.android.server.display.layout.Layout.Display display222 =
+                layout.getByAddress(DisplayAddress.fromPhysicalDisplayId(222));
+        assertEquals(display111.getLeadDisplayId(), layout.NO_LEAD_DISPLAY);
+        assertEquals(display222.getLeadDisplayId(), display111.getLogicalDisplayId());
+    }
+
+    @Test
+    public void testLeadDisplayAddress_defaultDisplay() {
+        Layout layout = new Layout();
+        createDefaultDisplay(layout, 123456L);
+
+        layout.postProcessLocked();
+
+        com.android.server.display.layout.Layout.Display display =
+                layout.getByAddress(DisplayAddress.fromPhysicalDisplayId(123456));
+        assertEquals(display.getLeadDisplayId(), layout.NO_LEAD_DISPLAY);
+    }
+
+    @Test
+    public void testLeadDisplayAddress_noLeadDisplay() {
+        Layout layout = new Layout();
+        createNonDefaultDisplay(layout, 222L, /* enabled= */ true, /* group= */ null,
+                /* leadDisplayAddress= */ null);
+
+        layout.postProcessLocked();
+
+        com.android.server.display.layout.Layout.Display display =
+                layout.getByAddress(DisplayAddress.fromPhysicalDisplayId(222));
+        assertEquals(display.getLeadDisplayId(), layout.NO_LEAD_DISPLAY);
+    }
+
+    @Test
+    public void testLeadDisplayAddress_selfLeadDisplayForNonDefaultDisplay() {
+        Layout layout = new Layout();
+
+        assertThrows("Expected Layout to throw IllegalArgumentException when the display points out"
+                + " itself as a lead display",
+                IllegalArgumentException.class,
+                () -> layout.createDisplayLocked(DisplayAddress.fromPhysicalDisplayId(123L),
+                    /* isDefault= */ true, /* isEnabled= */ true, /* displayGroupName= */ null,
+                    mDisplayIdProducerMock,  Layout.Display.POSITION_FRONT,
+                    DisplayAddress.fromPhysicalDisplayId(123L),
+                    /* brightnessThrottlingMapId= */ null, /* refreshRateZoneId= */ null,
+                    /* refreshRateThermalThrottlingMapId= */ null));
+    }
+
+    @Test
+    public void testLeadDisplayAddress_wrongLeadDisplayForDefaultDisplay() {
+        Layout layout = new Layout();
+
+        assertThrows("Expected Layout to throw IllegalArgumentException when the default display "
+                + "has a lead display",
+                IllegalArgumentException.class,
+                () -> layout.createDisplayLocked(DisplayAddress.fromPhysicalDisplayId(123L),
+                    /* isDefault= */ true, /* isEnabled= */ true, /* displayGroupName= */ null,
+                    mDisplayIdProducerMock,  Layout.Display.POSITION_FRONT,
+                    DisplayAddress.fromPhysicalDisplayId(987L),
+                    /* brightnessThrottlingMapId= */ null, /* refreshRateZoneId= */ null,
+                    /* refreshRateThermalThrottlingMapId= */ null));
+    }
+
+    @Test
+    public void testLeadDisplayAddress_notExistingLeadDisplayForNonDefaultDisplay() {
+        Layout layout = new Layout();
+        createNonDefaultDisplay(layout, 222L, /* enabled= */ true, /* group= */ null,
+                DisplayAddress.fromPhysicalDisplayId(111L));
+
+        assertThrows("Expected Layout to throw IllegalArgumentException when a lead display doesn't"
+                + " exist", IllegalArgumentException.class, () -> layout.postProcessLocked());
+    }
+
+    @Test
+    public void testLeadDisplayAddress_leadDisplayInDifferentDisplayGroup() {
+        Layout layout = new Layout();
+        createNonDefaultDisplay(layout, 111, /* enabled= */ true, /* group= */ "group1",
+                /* leadDisplayAddress= */ null);
+        createNonDefaultDisplay(layout, 222L, /* enabled= */ true, /* group= */ "group2",
+                DisplayAddress.fromPhysicalDisplayId(111L));
+
+        assertThrows("Expected Layout to throw IllegalArgumentException when pointing to a lead "
+                + "display in the different group",
+                IllegalArgumentException.class, () -> layout.postProcessLocked());
+    }
+
+    @Test
+    public void testLeadDisplayAddress_cyclicLeadDisplay() {
+        Layout layout = new Layout();
+        createNonDefaultDisplay(layout, 111, /* enabled= */ true, /* group= */ null,
+                DisplayAddress.fromPhysicalDisplayId(222L));
+        createNonDefaultDisplay(layout, 222L, /* enabled= */ true, /* group= */ null,
+                DisplayAddress.fromPhysicalDisplayId(333L));
+        createNonDefaultDisplay(layout, 333L, /* enabled= */ true, /* group= */ null,
+                DisplayAddress.fromPhysicalDisplayId(222L));
+
+        assertThrows("Expected Layout to throw IllegalArgumentException when pointing to a lead "
+                + "display in the different group",
+                IllegalArgumentException.class, () -> layout.postProcessLocked());
+    }
+
     ////////////////////
     // Helper Methods //
     ////////////////////
@@ -145,10 +265,11 @@
                 mDisplayIdProducerMock);
     }
 
-    private void createNonDefaultDisplay(Layout layout, long id, boolean enabled, String group) {
+    private void createNonDefaultDisplay(Layout layout, long id, boolean enabled, String group,
+            DisplayAddress leadDisplayAddress) {
         layout.createDisplayLocked(DisplayAddress.fromPhysicalDisplayId(id), /* isDefault= */ false,
                 enabled, group, mDisplayIdProducerMock, Layout.Display.POSITION_UNKNOWN,
-                Display.DEFAULT_DISPLAY, /* brightnessThrottlingMapId= */ null,
+                leadDisplayAddress, /* brightnessThrottlingMapId= */ null,
                 /* refreshRateZoneId= */ null,
                 /* refreshRateThermalThrottlingMapId= */ null);
     }
@@ -177,6 +298,10 @@
                 +      "<display enabled=\"false\" displayGroup=\"group2\">\n"
                 +        "<address>786</address>\n"
                 +      "</display>\n"
+                +      "<display enabled=\"true\">\n"
+                +        "<address>1092</address>\n"
+                +        "<leadDisplayAddress>78910</leadDisplayAddress>\n"
+                +      "</display>\n"
                 +    "</layout>\n"
 
                 +    "<layout>\n"
diff --git a/services/tests/displayservicetests/src/com/android/server/display/LogicalDisplayMapperTest.java b/services/tests/displayservicetests/src/com/android/server/display/LogicalDisplayMapperTest.java
index 1eec70d..3e695c9 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/LogicalDisplayMapperTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/LogicalDisplayMapperTest.java
@@ -604,13 +604,13 @@
         layout.createDisplayLocked(device1.getDisplayDeviceInfoLocked().address,
                 /* isDefault= */ true, /* isEnabled= */ true, /* displayGroup= */ null,
                 mIdProducer, POSITION_UNKNOWN,
-                /* leadDisplayId= */ Display.DEFAULT_DISPLAY,
+                /* leadDisplayAddress= */ null,
                 /* brightnessThrottlingMapId= */ "concurrent",
                 /* refreshRateZoneId= */ null, /* refreshRateThermalThrottlingMapId= */ null);
         layout.createDisplayLocked(device2.getDisplayDeviceInfoLocked().address,
                 /* isDefault= */ false, /* isEnabled= */ true, /* displayGroup= */ null,
                 mIdProducer, POSITION_UNKNOWN,
-                /* leadDisplayId= */ Display.DEFAULT_DISPLAY,
+                /* leadDisplayAddress= */ null,
                 /* brightnessThrottlingMapId= */ "concurrent",
                 /* refreshRateZoneId= */ null, /* refreshRateThermalThrottlingMapId= */ null);
         when(mDeviceStateToLayoutMapSpy.get(0)).thenReturn(layout);
@@ -646,7 +646,7 @@
         assertFalse(mLogicalDisplayMapper.getDisplayLocked(device2).isInTransitionLocked());
         assertEquals(-1, mLogicalDisplayMapper.getDisplayLocked(device1)
                 .getLeadDisplayIdLocked());
-        assertEquals(0, mLogicalDisplayMapper.getDisplayLocked(device2)
+        assertEquals(-1, mLogicalDisplayMapper.getDisplayLocked(device2)
                 .getLeadDisplayIdLocked());
         assertEquals("concurrent", mLogicalDisplayMapper.getDisplayLocked(device1)
                 .getDisplayInfoLocked().thermalBrightnessThrottlingDataId);
@@ -811,7 +811,7 @@
                 mIdProducer);
         layout.createDisplayLocked(device2.getDisplayDeviceInfoLocked().address,
                 /* isDefault= */ false, /* isEnabled= */ true, /* displayGroupName= */ null,
-                mIdProducer, POSITION_REAR, Display.DEFAULT_DISPLAY,
+                mIdProducer, POSITION_REAR, /* leadDisplayAddress= */ null,
                 /* brightnessThrottlingMapId= */ null, /* refreshRateZoneId= */ null,
                 /* refreshRateThermalThrottlingMapId= */null);
         when(mDeviceStateToLayoutMapSpy.get(0)).thenReturn(layout);
@@ -861,7 +861,7 @@
     private void createNonDefaultDisplay(Layout layout, DisplayAddress address, boolean enabled,
             String group) {
         layout.createDisplayLocked(address, /* isDefault= */ false, enabled, group, mIdProducer,
-                Layout.Display.POSITION_UNKNOWN, Display.DEFAULT_DISPLAY,
+                Layout.Display.POSITION_UNKNOWN, /* leadDisplayAddress= */ null,
                 /* brightnessThrottlingMapId= */ null, /* refreshRateZoneId= */ null,
                 /* refreshRateThermalThrottlingMapId= */ null);
     }
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/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/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
index ba07439..30180e8 100755
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -62,6 +62,8 @@
 import static android.os.Build.VERSION_CODES.O_MR1;
 import static android.os.Build.VERSION_CODES.P;
 import static android.os.PowerManager.PARTIAL_WAKE_LOCK;
+import static android.os.PowerWhitelistManager.REASON_NOTIFICATION_SERVICE;
+import static android.os.PowerWhitelistManager.TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_ALLOWED;
 import static android.os.UserHandle.USER_SYSTEM;
 import static android.os.UserManager.USER_TYPE_FULL_SECONDARY;
 import static android.os.UserManager.USER_TYPE_PROFILE_CLONE;
@@ -83,6 +85,9 @@
 import static com.android.internal.config.sysui.SystemUiSystemPropertiesFlags.NotificationFlags.SHOW_STICKY_HUN_FOR_DENIED_FSI;
 import static com.android.internal.config.sysui.SystemUiSystemPropertiesFlags.NotificationFlags.WAKE_LOCK_FOR_POSTING_NOTIFICATION;
 import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN;
+import static com.android.server.am.PendingIntentRecord.FLAG_ACTIVITY_SENDER;
+import static com.android.server.am.PendingIntentRecord.FLAG_BROADCAST_SENDER;
+import static com.android.server.am.PendingIntentRecord.FLAG_SERVICE_SENDER;
 import static com.android.server.notification.NotificationManagerService.DEFAULT_MAX_NOTIFICATION_ENQUEUE_RATE;
 import static com.android.server.notification.NotificationRecordLogger.NotificationReportedEvent.NOTIFICATION_ADJUSTED;
 import static com.android.server.notification.NotificationRecordLogger.NotificationReportedEvent.NOTIFICATION_POSTED;
@@ -186,6 +191,7 @@
 import android.os.IBinder;
 import android.os.Looper;
 import android.os.Parcel;
+import android.os.Parcelable;
 import android.os.PowerManager;
 import android.os.PowerManager.WakeLock;
 import android.os.Process;
@@ -716,6 +722,7 @@
 
     @After
     public void assertAllWakeLocksReleased() {
+        waitForIdle(); // Finish async work.
         for (WakeLock wakeLock : mAcquiredWakeLocks) {
             assertThat(wakeLock.isHeld()).isFalse();
         }
@@ -867,13 +874,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);
     }
@@ -5905,6 +5917,26 @@
     }
 
     @Test
+    public void testVisitUris_wearableExtender() {
+        Icon actionIcon = Icon.createWithContentUri("content://media/action");
+        Icon wearActionIcon = Icon.createWithContentUri("content://media/wearAction");
+        PendingIntent intent = PendingIntent.getActivity(mContext, 0, new Intent(),
+                PendingIntent.FLAG_IMMUTABLE);
+        Notification n = new Notification.Builder(mContext, "a")
+                .setSmallIcon(android.R.drawable.sym_def_app_icon)
+                .addAction(new Notification.Action.Builder(actionIcon, "Hey!", intent).build())
+                .extend(new Notification.WearableExtender().addAction(
+                        new Notification.Action.Builder(wearActionIcon, "Wear!", intent).build()))
+                .build();
+
+        Consumer<Uri> visitor = (Consumer<Uri>) spy(Consumer.class);
+        n.visitUris(visitor);
+
+        verify(visitor).accept(eq(actionIcon.getUri()));
+        verify(visitor).accept(eq(wearActionIcon.getUri()));
+    }
+
+    @Test
     public void testSetNotificationPolicy_preP_setOldFields() {
         ZenModeHelper mZenModeHelper = mock(ZenModeHelper.class);
         mService.mZenModeHelper = mZenModeHelper;
@@ -10734,6 +10766,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);
@@ -12047,6 +12124,70 @@
         assertThat(mService.mNotificationList.get(0).getNotification().when).isEqualTo(111); // old
     }
 
+    @Test
+    public void enqueueNotification_allowlistsPendingIntents() throws RemoteException {
+        PendingIntent contentIntent = createPendingIntent("content");
+        PendingIntent actionIntent1 = createPendingIntent("action1");
+        PendingIntent actionIntent2 = createPendingIntent("action2");
+        Notification n = new Notification.Builder(mContext, TEST_CHANNEL_ID)
+                .setContentIntent(contentIntent)
+                .addAction(new Notification.Action.Builder(null, "action1", actionIntent1).build())
+                .addAction(new Notification.Action.Builder(null, "action2", actionIntent2).build())
+                .build();
+
+        mBinderService.enqueueNotificationWithTag(PKG, PKG, "tag", 1,
+                parcelAndUnparcel(n, Notification.CREATOR), mUserId);
+
+        verify(mAmi, times(3)).setPendingIntentAllowlistDuration(
+                any(), any(), anyLong(),
+                eq(TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_ALLOWED),
+                eq(REASON_NOTIFICATION_SERVICE), any());
+        verify(mAmi, times(3)).setPendingIntentAllowBgActivityStarts(any(),
+                any(), eq(FLAG_ACTIVITY_SENDER | FLAG_BROADCAST_SENDER | FLAG_SERVICE_SENDER));
+    }
+
+    @Test
+    public void enqueueNotification_allowlistsPendingIntents_includingFromPublicVersion()
+            throws RemoteException {
+        PendingIntent contentIntent = createPendingIntent("content");
+        PendingIntent actionIntent = createPendingIntent("action");
+        PendingIntent publicContentIntent = createPendingIntent("publicContent");
+        PendingIntent publicActionIntent = createPendingIntent("publicAction");
+        Notification source = new Notification.Builder(mContext, TEST_CHANNEL_ID)
+                .setContentIntent(contentIntent)
+                .addAction(new Notification.Action.Builder(null, "action", actionIntent).build())
+                .setPublicVersion(new Notification.Builder(mContext, "channel")
+                        .setContentIntent(publicContentIntent)
+                        .addAction(new Notification.Action.Builder(
+                                null, "publicAction", publicActionIntent).build())
+                        .build())
+                .build();
+
+        mBinderService.enqueueNotificationWithTag(PKG, PKG, "tag", 1,
+                parcelAndUnparcel(source, Notification.CREATOR), mUserId);
+
+        verify(mAmi, times(4)).setPendingIntentAllowlistDuration(
+                any(), any(), anyLong(),
+                eq(TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_ALLOWED),
+                eq(REASON_NOTIFICATION_SERVICE), any());
+        verify(mAmi, times(4)).setPendingIntentAllowBgActivityStarts(any(),
+                any(), eq(FLAG_ACTIVITY_SENDER | FLAG_BROADCAST_SENDER | FLAG_SERVICE_SENDER));
+    }
+
+    private static <T extends Parcelable> T parcelAndUnparcel(T source,
+            Parcelable.Creator<T> creator) {
+        Parcel parcel = Parcel.obtain();
+        source.writeToParcel(parcel, 0);
+        parcel.setDataPosition(0);
+        return creator.createFromParcel(parcel);
+    }
+
+    private PendingIntent createPendingIntent(String action) {
+        return PendingIntent.getActivity(mContext, 0,
+                new Intent(action).setPackage(mContext.getPackageName()),
+                PendingIntent.FLAG_MUTABLE);
+    }
+
     private void setDpmAppOppsExemptFromDismissal(boolean isOn) {
         DeviceConfig.setProperty(
                 DeviceConfig.NAMESPACE_DEVICE_POLICY_MANAGER,
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationVisitUrisTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationVisitUrisTest.java
index d32289d..121e296 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationVisitUrisTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationVisitUrisTest.java
@@ -67,6 +67,8 @@
 import java.lang.reflect.Executable;
 import java.lang.reflect.Method;
 import java.lang.reflect.Modifier;
+import java.lang.reflect.ParameterizedType;
+import java.lang.reflect.Type;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Comparator;
@@ -88,7 +90,6 @@
     // This list should be emptied! Items can be removed as bugs are fixed.
     private static final Multimap<Class<?>, String> KNOWN_BAD =
             ImmutableMultimap.<Class<?>, String>builder()
-                    .put(Notification.WearableExtender.class, "addAction") // TODO: b/281044385
                     .put(Person.Builder.class, "setUri") // TODO: b/281044385
                     .put(RemoteViews.class, "setRemoteAdapter") // TODO: b/281044385
                     .build();
@@ -164,7 +165,7 @@
                 /* extenderClass= */ Notification.WearableExtender.class,
                 /* actionExtenderClass= */ Notification.Action.WearableExtender.class,
                 /* includeRemoteViews= */ true);
-        assertThat(notification.includedUris.size()).isAtLeast(730);
+        assertThat(notification.includedUris.size()).isAtLeast(900);
     }
 
     @Test
@@ -450,19 +451,43 @@
             Set<Class<?>> excludingClasses, SpecialParameterGenerator specialGenerator) {
         Log.i(TAG, "About to generate parameters for " + ReflectionUtils.methodToString(executable)
                 + " in " + where);
-        Class<?>[] parameterTypes = executable.getParameterTypes();
+        Type[] parameterTypes = executable.getGenericParameterTypes();
         Object[] parameterValues = new Object[parameterTypes.length];
         for (int i = 0; i < parameterTypes.length; i++) {
-            parameterValues[i] = generateObject(
+            parameterValues[i] = generateParameter(
                     parameterTypes[i],
                     where.plus(executable,
-                            String.format("[%d,%s]", i, parameterTypes[i].getName())),
+                            String.format("[%d,%s]", i, parameterTypes[i].getTypeName())),
                     excludingClasses,
                     specialGenerator);
         }
         return parameterValues;
     }
 
+    private static Object generateParameter(Type parameterType, Location where,
+            Set<Class<?>> excludingClasses, SpecialParameterGenerator specialGenerator) {
+        if (parameterType instanceof Class<?> parameterClass) {
+            return generateObject(
+                    parameterClass,
+                    where,
+                    excludingClasses,
+                    specialGenerator);
+        } else if (parameterType instanceof ParameterizedType parameterizedType) {
+            if (parameterizedType.getRawType().equals(List.class)
+                    && parameterizedType.getActualTypeArguments()[0] instanceof Class<?>) {
+                ArrayList listValue = new ArrayList();
+                for (int i = 0; i < NUM_ELEMENTS_IN_ARRAY; i++) {
+                    listValue.add(
+                            generateObject((Class<?>) parameterizedType.getActualTypeArguments()[0],
+                                    where, excludingClasses, specialGenerator));
+                }
+                return listValue;
+            }
+        }
+        throw new IllegalArgumentException(
+                "I have no idea how to produce a(n) " + parameterType + ", sorry");
+    }
+
     private static class ReflectionUtils {
         static Set<Class<?>> getConcreteSubclasses(Class<?> clazz, Class<?> containerClass) {
             return Arrays.stream(containerClass.getDeclaredClasses())
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/ActivityRecordTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
index 2e055e8..3db53eb 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
@@ -3354,7 +3354,8 @@
         // to client if the app didn't request IME visible.
         assertFalse(app2.mActivityRecord.mImeInsetsFrozenUntilStartInput);
         verify(app2.mClient, atLeastOnce()).resized(any(), anyBoolean(), any(),
-                insetsStateCaptor.capture(), anyBoolean(), anyInt(), anyInt(), anyBoolean());
+                insetsStateCaptor.capture(), anyBoolean(), anyBoolean(), anyInt(), anyInt(),
+                anyBoolean());
         assertFalse(app2.getInsetsState().isSourceOrDefaultVisible(ID_IME, ime()));
     }
 
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/TestIWindow.java b/services/tests/wmtests/src/com/android/server/wm/TestIWindow.java
index 40f86dd..3f8acc6 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TestIWindow.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TestIWindow.java
@@ -46,7 +46,8 @@
     @Override
     public void resized(ClientWindowFrames frames, boolean reportDraw,
             MergedConfiguration mergedConfig, InsetsState insetsState, boolean forceLayout,
-            int displayId, int seqId, boolean dragResizing) throws RemoteException {
+            boolean alwaysConsumeSystemBars, int displayId, int seqId, boolean dragResizing)
+            throws RemoteException {
     }
 
     @Override
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java
index 2d8ddfa..a82459f 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java
@@ -64,10 +64,19 @@
 import static org.junit.Assert.assertTrue;
 import static org.mockito.Mockito.clearInvocations;
 
+import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.content.pm.ActivityInfo;
 import android.content.res.Configuration;
 import android.graphics.Rect;
+import android.os.Binder;
+import android.os.DeadObjectException;
+import android.os.IBinder;
+import android.os.IInterface;
+import android.os.Parcel;
 import android.os.RemoteException;
+import android.os.ResultReceiver;
+import android.os.ShellCallback;
 import android.platform.test.annotations.Presubmit;
 import android.view.IRemoteAnimationFinishedCallback;
 import android.view.IRemoteAnimationRunner;
@@ -87,8 +96,10 @@
 import org.mockito.ArgumentCaptor;
 import org.mockito.Mockito;
 
+import java.io.FileDescriptor;
 import java.util.ArrayList;
 import java.util.Comparator;
+import java.util.NoSuchElementException;
 
 
 /**
@@ -1404,7 +1415,7 @@
     }
 
     @Test
-    public void testAddLocalInsetsSourceProvider() {
+    public void testAddLocalInsetsFrameProvider() {
          /*
                 ___ rootTask _______________________________________________
                |        |                |                                  |
@@ -1435,19 +1446,20 @@
                 TYPE_BASE_APPLICATION);
         attrs2.setTitle("AppWindow2");
         activity2.addWindow(createWindowState(attrs2, activity2));
+        final Binder owner = new Binder();
         Rect genericOverlayInsetsRect1 = new Rect(0, 200, 1080, 700);
         Rect genericOverlayInsetsRect2 = new Rect(0, 0, 1080, 200);
         final InsetsFrameProvider provider1 =
-                new InsetsFrameProvider(null, 1, WindowInsets.Type.systemOverlays())
+                new InsetsFrameProvider(owner, 1, WindowInsets.Type.systemOverlays())
                         .setArbitraryRectangle(genericOverlayInsetsRect1);
         final InsetsFrameProvider provider2 =
-                new InsetsFrameProvider(null, 2, WindowInsets.Type.systemOverlays())
+                new InsetsFrameProvider(owner, 2, WindowInsets.Type.systemOverlays())
                         .setArbitraryRectangle(genericOverlayInsetsRect2);
         final int sourceId1 = provider1.getId();
         final int sourceId2 = provider2.getId();
 
-        rootTask.addLocalInsetsFrameProvider(provider1);
-        container.addLocalInsetsFrameProvider(provider2);
+        rootTask.addLocalInsetsFrameProvider(provider1, owner);
+        container.addLocalInsetsFrameProvider(provider2, owner);
 
         InsetsSource genericOverlayInsetsProvider1Source = new InsetsSource(
                 sourceId1, systemOverlays());
@@ -1479,7 +1491,7 @@
     }
 
     @Test
-    public void testAddLocalInsetsSourceProvider_sameType_replacesInsets() {
+    public void testAddLocalInsetsFrameProvider_sameType_replacesInsets() {
          /*
                 ___ rootTask ________________________________________
                |                  |                                  |
@@ -1494,24 +1506,25 @@
         attrs.setTitle("AppWindow0");
         activity0.addWindow(createWindowState(attrs, activity0));
 
+        final Binder owner = new Binder();
         final Rect genericOverlayInsetsRect1 = new Rect(0, 200, 1080, 700);
         final Rect genericOverlayInsetsRect2 = new Rect(0, 0, 1080, 200);
         final InsetsFrameProvider provider1 =
-                new InsetsFrameProvider(null, 1, WindowInsets.Type.systemOverlays())
+                new InsetsFrameProvider(owner, 1, WindowInsets.Type.systemOverlays())
                         .setArbitraryRectangle(genericOverlayInsetsRect1);
         final InsetsFrameProvider provider2 =
-                new InsetsFrameProvider(null, 1, WindowInsets.Type.systemOverlays())
+                new InsetsFrameProvider(owner, 1, WindowInsets.Type.systemOverlays())
                         .setArbitraryRectangle(genericOverlayInsetsRect2);
         final int sourceId1 = provider1.getId();
         final int sourceId2 = provider2.getId();
 
-        rootTask.addLocalInsetsFrameProvider(provider1);
+        rootTask.addLocalInsetsFrameProvider(provider1, owner);
         activity0.forAllWindows(window -> {
             assertEquals(genericOverlayInsetsRect1,
                     window.getInsetsState().peekSource(sourceId1).getFrame());
         }, true);
 
-        rootTask.addLocalInsetsFrameProvider(provider2);
+        rootTask.addLocalInsetsFrameProvider(provider2, owner);
 
         activity0.forAllWindows(window -> {
             assertEquals(genericOverlayInsetsRect2,
@@ -1520,7 +1533,7 @@
     }
 
     @Test
-    public void testRemoveLocalInsetsSourceProvider() {
+    public void testRemoveLocalInsetsFrameProvider() {
          /*
                 ___ rootTask _______________________________________________
                |        |                |                                  |
@@ -1554,21 +1567,22 @@
 
         activity2.addWindow(createWindowState(attrs2, activity2));
 
+        final Binder owner = new Binder();
         final Rect navigationBarInsetsRect1 = new Rect(0, 200, 1080, 700);
         final Rect navigationBarInsetsRect2 = new Rect(0, 0, 1080, 200);
         final InsetsFrameProvider provider1 =
-                new InsetsFrameProvider(null, 1, WindowInsets.Type.systemOverlays())
+                new InsetsFrameProvider(owner, 1, WindowInsets.Type.systemOverlays())
                         .setArbitraryRectangle(navigationBarInsetsRect1);
         final InsetsFrameProvider provider2 =
-                new InsetsFrameProvider(null, 2, WindowInsets.Type.systemOverlays())
+                new InsetsFrameProvider(owner, 2, WindowInsets.Type.systemOverlays())
                         .setArbitraryRectangle(navigationBarInsetsRect2);
         final int sourceId1 = provider1.getId();
         final int sourceId2 = provider2.getId();
 
-        rootTask.addLocalInsetsFrameProvider(provider1);
-        container.addLocalInsetsFrameProvider(provider2);
+        rootTask.addLocalInsetsFrameProvider(provider1, owner);
+        container.addLocalInsetsFrameProvider(provider2, owner);
         mDisplayContent.getInsetsStateController().onPostLayout();
-        rootTask.removeLocalInsetsFrameProvider(provider1);
+        rootTask.removeLocalInsetsFrameProvider(provider1, owner);
         mDisplayContent.getInsetsStateController().onPostLayout();
 
         activity0.forAllWindows(window -> {
@@ -1593,6 +1607,67 @@
         }, true);
     }
 
+    @Test
+    public void testAddLocalInsetsFrameProvider_ownerDiesAfterAdding() {
+        final Task task = createTask(mDisplayContent);
+        final TestBinder owner = new TestBinder();
+        final InsetsFrameProvider provider =
+                new InsetsFrameProvider(owner, 0, WindowInsets.Type.systemOverlays())
+                        .setArbitraryRectangle(new Rect());
+        task.addLocalInsetsFrameProvider(provider, owner);
+
+        assertTrue("The death recipient must exist.", owner.hasDeathRecipient());
+        assertTrue("The source must be added.", hasLocalSource(task, provider.getId()));
+
+        // The owner dies after adding the source.
+        owner.die();
+
+        assertFalse("The death recipient must be removed.", owner.hasDeathRecipient());
+        assertFalse("The source must be removed.", hasLocalSource(task, provider.getId()));
+    }
+
+    @Test
+    public void testAddLocalInsetsFrameProvider_ownerDiesBeforeAdding() {
+        final Task task = createTask(mDisplayContent);
+        final TestBinder owner = new TestBinder();
+
+        // The owner dies before adding the source.
+        owner.die();
+
+        final InsetsFrameProvider provider =
+                new InsetsFrameProvider(owner, 0, WindowInsets.Type.systemOverlays())
+                        .setArbitraryRectangle(new Rect());
+        task.addLocalInsetsFrameProvider(provider, owner);
+
+        assertFalse("The death recipient must not exist.", owner.hasDeathRecipient());
+        assertFalse("The source must not be added.", hasLocalSource(task, provider.getId()));
+    }
+
+    @Test
+    public void testRemoveLocalInsetsFrameProvider_removeDeathRecipient() {
+        final Task task = createTask(mDisplayContent);
+        final TestBinder owner = new TestBinder();
+        final InsetsFrameProvider provider =
+                new InsetsFrameProvider(owner, 0, WindowInsets.Type.systemOverlays())
+                        .setArbitraryRectangle(new Rect());
+        task.addLocalInsetsFrameProvider(provider, owner);
+
+        assertTrue("The death recipient must exist.", owner.hasDeathRecipient());
+        assertTrue("The source must be added.", hasLocalSource(task, provider.getId()));
+
+        task.removeLocalInsetsFrameProvider(provider, owner);
+
+        assertFalse("The death recipient must be removed.", owner.hasDeathRecipient());
+        assertFalse("The source must be removed.", hasLocalSource(task, provider.getId()));
+    }
+
+    private static boolean hasLocalSource(WindowContainer container, int sourceId) {
+        if (container.mLocalInsetsSources == null) {
+            return false;
+        }
+        return container.mLocalInsetsSources.contains(sourceId);
+    }
+
     /* Used so we can gain access to some protected members of the {@link WindowContainer} class */
     private static class TestWindowContainer extends WindowContainer<TestWindowContainer> {
         private final int mLayer;
@@ -1797,4 +1872,82 @@
             mIsVisibleRequested = isVisibleRequested;
         }
     }
+
+    private static class TestBinder implements IBinder {
+
+        private boolean mDead;
+        private final ArrayList<IBinder.DeathRecipient> mDeathRecipients = new ArrayList<>();
+
+        public void die() {
+            mDead = true;
+            for (int i = mDeathRecipients.size() - 1; i >= 0; i--) {
+                final DeathRecipient recipient = mDeathRecipients.get(i);
+                recipient.binderDied(this);
+            }
+        }
+
+        public boolean hasDeathRecipient() {
+            return !mDeathRecipients.isEmpty();
+        }
+
+        @Override
+        public void linkToDeath(@NonNull DeathRecipient recipient, int flags)
+                throws RemoteException {
+            if (mDead) {
+                throw new DeadObjectException();
+            }
+            mDeathRecipients.add(recipient);
+        }
+
+        @Override
+        public boolean unlinkToDeath(@NonNull DeathRecipient recipient, int flags) {
+            final boolean successes = mDeathRecipients.remove(recipient);
+            if (successes || mDead) {
+                return successes;
+            }
+            throw new NoSuchElementException("Given recipient has not been registered.");
+        }
+
+        @Override
+        public boolean isBinderAlive() {
+            return !mDead;
+        }
+
+        @Override
+        public boolean pingBinder() {
+            return !mDead;
+        }
+
+        @Nullable
+        @Override
+        public String getInterfaceDescriptor() throws RemoteException {
+            return null;
+        }
+
+        @Nullable
+        @Override
+        public IInterface queryLocalInterface(@NonNull String descriptor) {
+            return null;
+        }
+
+        @Override
+        public void dump(@NonNull FileDescriptor fd, @Nullable String[] args)
+                throws RemoteException { }
+
+        @Override
+        public void dumpAsync(@NonNull FileDescriptor fd, @Nullable String[] args)
+                throws RemoteException { }
+
+        @Override
+        public void shellCommand(@Nullable FileDescriptor in, @Nullable FileDescriptor out,
+                @Nullable FileDescriptor err, @NonNull String[] args,
+                @Nullable ShellCallback shellCallback,
+                @NonNull ResultReceiver resultReceiver) throws RemoteException { }
+
+        @Override
+        public boolean transact(int code, @NonNull Parcel data, @Nullable Parcel reply, int flags)
+                throws RemoteException {
+            return false;
+        }
+    }
 }
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
index c13c2c3..0ddd31355 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
@@ -778,8 +778,8 @@
             doThrow(new RemoteException("test")).when(win.mClient).resized(any() /* frames */,
                     anyBoolean() /* reportDraw */, any() /* mergedConfig */,
                     any() /* insetsState */, anyBoolean() /* forceLayout */,
-                    anyInt() /* displayId */, anyInt() /* seqId */,
-                    anyBoolean() /* dragResizing */);
+                    anyBoolean() /* alwaysConsumeSystemBars */, anyInt() /* displayId */,
+                    anyInt() /* seqId */, anyBoolean() /* dragResizing */);
         } catch (RemoteException ignored) {
         }
         win.reportResized();
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/FlickerTests/Android.bp b/tests/FlickerTests/Android.bp
index feae3b7..72b5159 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",
     ],
@@ -67,6 +68,13 @@
     srcs: ["src/**/rotation/*.kt"],
 }
 
+filegroup {
+    name: "FlickerServiceTests-src",
+    srcs: [
+        "src/com/android/server/wm/flicker/service/**/*.kt",
+    ],
+}
+
 java_defaults {
     name: "FlickerTestsDefault",
     manifest: "manifests/AndroidManifest.xml",
@@ -109,6 +117,7 @@
         ":FlickerTestsQuickswitch-src",
         ":FlickerTestsRotation-src",
         ":FlickerTestsNotification-src",
+        ":FlickerServiceTests-src",
     ],
 }
 
@@ -149,6 +158,9 @@
         ":FlickerTestsBase-src",
         ":FlickerTestsAppLaunch-src",
     ],
+    exclude_srcs: [
+        ":FlickerTestsActivityEmbedding-src",
+    ],
 }
 
 android_test {
@@ -190,6 +202,18 @@
     ],
 }
 
+android_test {
+    name: "FlickerServiceTests",
+    defaults: ["FlickerTestsDefault"],
+    additional_manifests: ["manifests/AndroidManifestService.xml"],
+    package_name: "com.android.server.wm.flicker.service",
+    instrumentation_target_package: "com.android.server.wm.flicker.service",
+    srcs: [
+        ":FlickerTestsBase-src",
+        ":FlickerServiceTests-src",
+    ],
+}
+
 java_library {
     name: "wm-flicker-common-assertions",
     platform_apis: true,
diff --git a/tests/FlickerTests/AndroidTestTemplate.xml b/tests/FlickerTests/AndroidTestTemplate.xml
index ed63ec0..44a8245 100644
--- a/tests/FlickerTests/AndroidTestTemplate.xml
+++ b/tests/FlickerTests/AndroidTestTemplate.xml
@@ -87,6 +87,8 @@
                 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="directory-keys"
+            value="/data/user/0/com.android.server.wm.flicker.service/files"/>
         <option name="collect-on-run-ended-only" value="true"/>
         <option name="clean-up" value="true"/>
     </metrics_collector>
diff --git a/tests/FlickerTests/manifests/AndroidManifestService.xml b/tests/FlickerTests/manifests/AndroidManifestService.xml
new file mode 100644
index 0000000..3a7bc509
--- /dev/null
+++ b/tests/FlickerTests/manifests/AndroidManifestService.xml
@@ -0,0 +1,24 @@
+<!--
+  ~ 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.
+  -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.android.server.wm.flicker.service">
+
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+                     android:targetPackage="com.android.server.wm.flicker.service"
+                     android:label="WindowManager Flicker Service Tests">
+    </instrumentation>
+</manifest>
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
index 43f4ce9..27de12e 100644
--- 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
@@ -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.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
@@ -32,8 +33,8 @@
 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.
+ * 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.
  *
@@ -45,8 +46,8 @@
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class OpenThirdActivityOverSplitTest (flicker: LegacyFlickerTest) :
-        ActivityEmbeddingTestBase(flicker) {
+class OpenThirdActivityOverSplitTest(flicker: LegacyFlickerTest) :
+    ActivityEmbeddingTestBase(flicker) {
     /** {@inheritDoc} */
     override val transition: FlickerBuilder.() -> Unit = {
         setup {
@@ -56,12 +57,10 @@
             testApp.launchSecondaryActivity(wmHelper)
 
             startDisplayBounds =
-                    wmHelper.currentState.layerState.physicalDisplayBounds
-                            ?: error("Can't get display bounds")
+                wmHelper.currentState.layerState.physicalDisplayBounds
+                    ?: error("Can't get display bounds")
         }
-        transitions {
-            testApp.launchThirdActivity(wmHelper)
-        }
+        transitions { testApp.launchThirdActivity(wmHelper) }
         teardown {
             tapl.goHome()
             testApp.exit(wmHelper)
@@ -79,36 +78,33 @@
     @Presubmit
     @Test
     fun mainActivityLayersAlwaysVisible() {
-        flicker.assertLayers {
-            isVisible(ActivityEmbeddingAppHelper.MAIN_ACTIVITY_COMPONENT)
-        }
+        flicker.assertLayers { isVisible(ActivityEmbeddingAppHelper.MAIN_ACTIVITY_COMPONENT) }
 
         flicker.assertLayersStart {
-            val display = this.entry.displays.firstOrNull { it.isOn && !it.isVirtual }
+            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)
+                this.visibleRegion(ActivityEmbeddingAppHelper.MAIN_ACTIVITY_COMPONENT)
             val secondaryActivityRegion =
-                    this.visibleRegion(ActivityEmbeddingAppHelper.SECONDARY_ACTIVITY_COMPONENT)
-                            .region
-            mainActivityRegion
-                    .plus(secondaryActivityRegion)
-                    .coversExactly(display.layerStackSpace)
+                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 }
+            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)
+                this.visibleRegion(ActivityEmbeddingAppHelper.MAIN_ACTIVITY_COMPONENT)
             val secondaryActivityRegion =
-                    this.visibleRegion(ActivityEmbeddingAppHelper.SECONDARY_ACTIVITY_COMPONENT)
+                this.visibleRegion(ActivityEmbeddingAppHelper.SECONDARY_ACTIVITY_COMPONENT)
             secondaryActivityRegion.isEmpty()
             val thirdActivityRegion =
-                    this.visibleRegion(ActivityEmbeddingAppHelper.THIRD_ACTIVITY_COMPONENT)
+                this.visibleRegion(ActivityEmbeddingAppHelper.THIRD_ACTIVITY_COMPONENT)
             mainActivityRegion
-                    .plus(thirdActivityRegion.region)
-                    .coversExactly(display.layerStackSpace)
+                .plus(thirdActivityRegion.region)
+                .coversExactly(display.layerStackSpace)
         }
     }
 
@@ -118,13 +114,15 @@
     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
+                .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
         }
     }
 
@@ -134,13 +132,13 @@
     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)
+                .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)
         }
     }
 
@@ -149,9 +147,7 @@
     @Test
     fun backgroundLayerNeverVisible() {
         val backgroundColorLayer = ComponentNameMatcher("", "Animation Background")
-        flicker.assertLayers {
-            isInvisible(backgroundColorLayer)
-        }
+        flicker.assertLayers { isInvisible(backgroundColorLayer) }
     }
 
     companion object {
@@ -167,4 +163,4 @@
         @JvmStatic
         fun getParams() = LegacyFlickerTestFactory.nonRotationTests()
     }
-}
\ No newline at end of file
+}
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/helpers/ActivityEmbeddingAppHelper.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ActivityEmbeddingAppHelper.kt
index 06beec1..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
@@ -62,18 +62,18 @@
     /** 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
-                )
+            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()
+            .StateSyncBuilder()
+            .withActivityState(MAIN_ACTIVITY_COMPONENT, PlatformConsts.STATE_RESUMED)
+            .withActivityState(SECONDARY_ACTIVITY_COMPONENT, PlatformConsts.STATE_STOPPED)
+            .withActivityState(THIRD_ACTIVITY_COMPONENT, PlatformConsts.STATE_RESUMED)
+            .waitForAndVerify()
     }
 
     /**
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/OpenAppFromLockscreenViaIntentTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockscreenViaIntentTest.kt
index 2a2a3db..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.traces.component.ComponentNameMatcher
 import android.tools.common.flicker.annotation.FlickerServiceCompatible
+import android.tools.common.traces.component.ComponentNameMatcher
 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/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/src/com/android/server/wm/flicker/service/Utils.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/service/Utils.kt
new file mode 100644
index 0000000..8242e9a
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/service/Utils.kt
@@ -0,0 +1,53 @@
+/*
+ * 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.service
+
+import android.app.Instrumentation
+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.device.apphelpers.MessagingAppHelper
+import android.tools.device.flicker.rules.ChangeDisplayOrientationRule
+import android.tools.device.flicker.rules.LaunchAppRule
+import android.tools.device.flicker.rules.RemoveAllTasksButHomeRule
+import androidx.test.platform.app.InstrumentationRegistry
+import org.junit.rules.RuleChain
+
+object Utils {
+    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())
+    }
+}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/service/close/flicker/CloseAppBackButton3ButtonLandscape.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/service/close/flicker/CloseAppBackButton3ButtonLandscape.kt
new file mode 100644
index 0000000..030b292
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/service/close/flicker/CloseAppBackButton3ButtonLandscape.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.server.wm.flicker.service.close.flicker
+
+import android.tools.common.NavBar
+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.server.wm.flicker.service.close.scenarios.CloseAppBackButton
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class CloseAppBackButton3ButtonLandscape :
+    CloseAppBackButton(NavBar.MODE_3BUTTON, Rotation.ROTATION_90) {
+    @ExpectedScenarios(["APP_CLOSE_TO_HOME"])
+    @Test
+    override fun closeAppBackButtonTest() = super.closeAppBackButtonTest()
+
+    companion object {
+        @JvmStatic
+        @FlickerConfigProvider
+        fun flickerConfigProvider(): FlickerConfig =
+            FlickerConfig().use(FlickerServiceConfig.DEFAULT)
+    }
+}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/service/close/flicker/CloseAppBackButton3ButtonPortrait.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/service/close/flicker/CloseAppBackButton3ButtonPortrait.kt
new file mode 100644
index 0000000..770da143
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/service/close/flicker/CloseAppBackButton3ButtonPortrait.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.server.wm.flicker.service.close.flicker
+
+import android.tools.common.NavBar
+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.server.wm.flicker.service.close.scenarios.CloseAppBackButton
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class CloseAppBackButton3ButtonPortrait :
+    CloseAppBackButton(NavBar.MODE_3BUTTON, Rotation.ROTATION_0) {
+    @ExpectedScenarios(["APP_CLOSE_TO_HOME"])
+    @Test
+    override fun closeAppBackButtonTest() = super.closeAppBackButtonTest()
+
+    companion object {
+        @JvmStatic
+        @FlickerConfigProvider
+        fun flickerConfigProvider(): FlickerConfig =
+            FlickerConfig().use(FlickerServiceConfig.DEFAULT)
+    }
+}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/service/close/flicker/CloseAppBackButtonGesturalNavLandscape.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/service/close/flicker/CloseAppBackButtonGesturalNavLandscape.kt
new file mode 100644
index 0000000..4b2206d7
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/service/close/flicker/CloseAppBackButtonGesturalNavLandscape.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.server.wm.flicker.service.close.flicker
+
+import android.tools.common.NavBar
+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.server.wm.flicker.service.close.scenarios.CloseAppBackButton
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class CloseAppBackButtonGesturalNavLandscape :
+    CloseAppBackButton(NavBar.MODE_GESTURAL, Rotation.ROTATION_90) {
+    @ExpectedScenarios(["APP_CLOSE_TO_HOME"])
+    @Test
+    override fun closeAppBackButtonTest() = super.closeAppBackButtonTest()
+
+    companion object {
+        @JvmStatic
+        @FlickerConfigProvider
+        fun flickerConfigProvider(): FlickerConfig =
+            FlickerConfig().use(FlickerServiceConfig.DEFAULT)
+    }
+}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/service/close/flicker/CloseAppBackButtonGesturalNavPortrait.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/service/close/flicker/CloseAppBackButtonGesturalNavPortrait.kt
new file mode 100644
index 0000000..54b471e
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/service/close/flicker/CloseAppBackButtonGesturalNavPortrait.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.server.wm.flicker.service.close.flicker
+
+import android.tools.common.NavBar
+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.server.wm.flicker.service.close.scenarios.CloseAppBackButton
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class CloseAppBackButtonGesturalNavPortrait :
+    CloseAppBackButton(NavBar.MODE_GESTURAL, Rotation.ROTATION_0) {
+    @ExpectedScenarios(["APP_CLOSE_TO_HOME"])
+    @Test
+    override fun closeAppBackButtonTest() = super.closeAppBackButtonTest()
+
+    companion object {
+        @JvmStatic
+        @FlickerConfigProvider
+        fun flickerConfigProvider(): FlickerConfig =
+            FlickerConfig().use(FlickerServiceConfig.DEFAULT)
+    }
+}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/service/close/flicker/CloseAppHomeButton3ButtonLandscape.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/service/close/flicker/CloseAppHomeButton3ButtonLandscape.kt
new file mode 100644
index 0000000..47c2529
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/service/close/flicker/CloseAppHomeButton3ButtonLandscape.kt
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm.flicker.service.close.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.server.wm.flicker.service.close.scenarios.CloseAppHomeButton
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class CloseAppHomeButton3ButtonLandscape : CloseAppHomeButton(Rotation.ROTATION_90) {
+    @ExpectedScenarios(["APP_CLOSE_TO_HOME"])
+    @Test
+    override fun closeAppHomeButton() = super.closeAppHomeButton()
+
+    companion object {
+        @JvmStatic
+        @FlickerConfigProvider
+        fun flickerConfigProvider(): FlickerConfig =
+            FlickerConfig().use(FlickerServiceConfig.DEFAULT)
+    }
+}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/service/close/flicker/CloseAppHomeButton3ButtonPortrait.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/service/close/flicker/CloseAppHomeButton3ButtonPortrait.kt
new file mode 100644
index 0000000..b18148f
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/service/close/flicker/CloseAppHomeButton3ButtonPortrait.kt
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm.flicker.service.close.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.server.wm.flicker.service.close.scenarios.CloseAppHomeButton
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class CloseAppHomeButton3ButtonPortrait : CloseAppHomeButton(Rotation.ROTATION_0) {
+    @ExpectedScenarios(["APP_CLOSE_TO_HOME"])
+    @Test
+    override fun closeAppHomeButton() = super.closeAppHomeButton()
+
+    companion object {
+        @JvmStatic
+        @FlickerConfigProvider
+        fun flickerConfigProvider(): FlickerConfig =
+            FlickerConfig().use(FlickerServiceConfig.DEFAULT)
+    }
+}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/service/close/flicker/CloseAppSwipeToHomeGesturalNavLandscape.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/service/close/flicker/CloseAppSwipeToHomeGesturalNavLandscape.kt
new file mode 100644
index 0000000..8543015
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/service/close/flicker/CloseAppSwipeToHomeGesturalNavLandscape.kt
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm.flicker.service.close.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.server.wm.flicker.service.close.scenarios.CloseAppSwipeToHome
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class CloseAppSwipeToHomeGesturalNavLandscape : CloseAppSwipeToHome(Rotation.ROTATION_90) {
+    @ExpectedScenarios(["APP_CLOSE_TO_HOME"])
+    @Test
+    override fun closeAppSwipeToHome() = super.closeAppSwipeToHome()
+
+    companion object {
+        @JvmStatic
+        @FlickerConfigProvider
+        fun flickerConfigProvider(): FlickerConfig =
+            FlickerConfig().use(FlickerServiceConfig.DEFAULT)
+    }
+}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/service/close/flicker/CloseAppSwipeToHomeGesturalNavPortrait.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/service/close/flicker/CloseAppSwipeToHomeGesturalNavPortrait.kt
new file mode 100644
index 0000000..f88963b
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/service/close/flicker/CloseAppSwipeToHomeGesturalNavPortrait.kt
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm.flicker.service.close.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.server.wm.flicker.service.close.scenarios.CloseAppSwipeToHome
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class CloseAppSwipeToHomeGesturalNavPortrait : CloseAppSwipeToHome(Rotation.ROTATION_0) {
+    @ExpectedScenarios(["APP_CLOSE_TO_HOME"])
+    @Test
+    override fun closeAppSwipeToHome() = super.closeAppSwipeToHome()
+
+    companion object {
+        @JvmStatic
+        @FlickerConfigProvider
+        fun flickerConfigProvider(): FlickerConfig =
+            FlickerConfig().use(FlickerServiceConfig.DEFAULT)
+    }
+}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/service/close/scenarios/CloseAppBackButton.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/service/close/scenarios/CloseAppBackButton.kt
new file mode 100644
index 0000000..2aaacde
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/service/close/scenarios/CloseAppBackButton.kt
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.service.close.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 com.android.launcher3.tapl.LauncherInstrumentation
+import com.android.server.wm.flicker.helpers.SimpleAppHelper
+import com.android.server.wm.flicker.service.Utils
+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 CloseAppBackButton(
+    val gestureMode: NavBar = NavBar.MODE_GESTURAL,
+    val rotation: Rotation = Rotation.ROTATION_0
+) {
+    private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
+    private val tapl = LauncherInstrumentation()
+    private val wmHelper = WindowManagerStateHelper(instrumentation)
+    private val testApp = SimpleAppHelper(instrumentation)
+
+    @Rule @JvmField val testSetupRule = Utils.testSetupRule(gestureMode, rotation)
+
+    @Before
+    fun setup() {
+        tapl.setExpectedRotation(rotation.value)
+        testApp.launchViaIntent(wmHelper)
+    }
+
+    @Test
+    open fun closeAppBackButtonTest() {
+        tapl.pressBack()
+        wmHelper.StateSyncBuilder().withHomeActivityVisible().waitForAndVerify()
+    }
+
+    @After
+    fun teardown() {
+        testApp.exit(wmHelper)
+    }
+}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/service/close/scenarios/CloseAppHomeButton.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/service/close/scenarios/CloseAppHomeButton.kt
new file mode 100644
index 0000000..08683a3
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/service/close/scenarios/CloseAppHomeButton.kt
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.service.close.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 com.android.launcher3.tapl.LauncherInstrumentation
+import com.android.server.wm.flicker.helpers.SimpleAppHelper
+import com.android.server.wm.flicker.service.Utils
+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 CloseAppHomeButton(val rotation: Rotation = Rotation.ROTATION_0) {
+    private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
+    private val tapl = LauncherInstrumentation()
+    private val wmHelper = WindowManagerStateHelper(instrumentation)
+    private val testApp = SimpleAppHelper(instrumentation)
+
+    @Rule @JvmField val testSetupRule = Utils.testSetupRule(NavBar.MODE_3BUTTON, rotation)
+
+    @Before
+    fun setup() {
+        tapl.setExpectedRotation(rotation.value)
+        testApp.launchViaIntent(wmHelper)
+        tapl.setExpectedRotationCheckEnabled(false)
+    }
+
+    @Test
+    open fun closeAppHomeButton() {
+        tapl.goHome()
+        wmHelper.StateSyncBuilder().withHomeActivityVisible().waitForAndVerify()
+    }
+
+    @After
+    fun teardown() {
+        testApp.exit(wmHelper)
+    }
+}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/service/close/scenarios/CloseAppSwipeToHome.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/service/close/scenarios/CloseAppSwipeToHome.kt
new file mode 100644
index 0000000..360e114
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/service/close/scenarios/CloseAppSwipeToHome.kt
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.service.close.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 com.android.launcher3.tapl.LauncherInstrumentation
+import com.android.server.wm.flicker.helpers.SimpleAppHelper
+import com.android.server.wm.flicker.service.Utils
+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 CloseAppSwipeToHome(val rotation: Rotation = Rotation.ROTATION_0) {
+    private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
+    private val tapl = LauncherInstrumentation()
+    private val wmHelper = WindowManagerStateHelper(instrumentation)
+    private val testApp = SimpleAppHelper(instrumentation)
+
+    @Rule @JvmField val testSetupRule = Utils.testSetupRule(NavBar.MODE_GESTURAL, rotation)
+
+    @Before
+    fun setup() {
+        tapl.setExpectedRotation(rotation.value)
+        testApp.launchViaIntent(wmHelper)
+        tapl.setExpectedRotationCheckEnabled(false)
+    }
+
+    @Test
+    open fun closeAppSwipeToHome() {
+        tapl.goHome()
+        wmHelper.StateSyncBuilder().withHomeActivityVisible().waitForAndVerify()
+    }
+
+    @After
+    fun teardown() {
+        testApp.exit(wmHelper)
+    }
+}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/flicker/OpenAppFromLockscreenNotificationCold3ButtonNavLandscape.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/flicker/OpenAppFromLockscreenNotificationCold3ButtonNavLandscape.kt
new file mode 100644
index 0000000..bb18770
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/flicker/OpenAppFromLockscreenNotificationCold3ButtonNavLandscape.kt
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm.flicker.service.notification.flicker
+
+import android.tools.common.NavBar
+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.server.wm.flicker.service.notification.scenarios.OpenAppFromLockscreenNotificationCold
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class OpenAppFromLockscreenNotificationCold3ButtonNavLandscape :
+    OpenAppFromLockscreenNotificationCold(NavBar.MODE_3BUTTON, Rotation.ROTATION_90) {
+    @ExpectedScenarios(["APP_LAUNCH_FROM_NOTIFICATION"])
+    @Test
+    override fun openAppFromLockscreenNotificationCold() =
+        super.openAppFromLockscreenNotificationCold()
+
+    companion object {
+        @JvmStatic
+        @FlickerConfigProvider
+        fun flickerConfigProvider(): FlickerConfig =
+            FlickerConfig().use(FlickerServiceConfig.DEFAULT)
+    }
+}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/flicker/OpenAppFromLockscreenNotificationCold3ButtonNavPortrait.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/flicker/OpenAppFromLockscreenNotificationCold3ButtonNavPortrait.kt
new file mode 100644
index 0000000..1c3cc21
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/flicker/OpenAppFromLockscreenNotificationCold3ButtonNavPortrait.kt
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm.flicker.service.notification.flicker
+
+import android.tools.common.NavBar
+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.server.wm.flicker.service.notification.scenarios.OpenAppFromLockscreenNotificationCold
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class OpenAppFromLockscreenNotificationCold3ButtonNavPortrait :
+    OpenAppFromLockscreenNotificationCold(NavBar.MODE_3BUTTON, Rotation.ROTATION_0) {
+    @ExpectedScenarios(["APP_LAUNCH_FROM_NOTIFICATION"])
+    @Test
+    override fun openAppFromLockscreenNotificationCold() =
+        super.openAppFromLockscreenNotificationCold()
+
+    companion object {
+        @JvmStatic
+        @FlickerConfigProvider
+        fun flickerConfigProvider(): FlickerConfig =
+            FlickerConfig().use(FlickerServiceConfig.DEFAULT)
+    }
+}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/flicker/OpenAppFromLockscreenNotificationColdGesturalNavLandscape.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/flicker/OpenAppFromLockscreenNotificationColdGesturalNavLandscape.kt
new file mode 100644
index 0000000..46d36db
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/flicker/OpenAppFromLockscreenNotificationColdGesturalNavLandscape.kt
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm.flicker.service.notification.flicker
+
+import android.tools.common.NavBar
+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.server.wm.flicker.service.notification.scenarios.OpenAppFromLockscreenNotificationCold
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class OpenAppFromLockscreenNotificationColdGesturalNavLandscape :
+    OpenAppFromLockscreenNotificationCold(NavBar.MODE_GESTURAL, Rotation.ROTATION_90) {
+    @ExpectedScenarios(["APP_LAUNCH_FROM_NOTIFICATION"])
+    @Test
+    override fun openAppFromLockscreenNotificationCold() =
+        super.openAppFromLockscreenNotificationCold()
+
+    companion object {
+        @JvmStatic
+        @FlickerConfigProvider
+        fun flickerConfigProvider(): FlickerConfig =
+            FlickerConfig().use(FlickerServiceConfig.DEFAULT)
+    }
+}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/flicker/OpenAppFromLockscreenNotificationColdGesturalNavPortrait.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/flicker/OpenAppFromLockscreenNotificationColdGesturalNavPortrait.kt
new file mode 100644
index 0000000..f6a668fe
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/flicker/OpenAppFromLockscreenNotificationColdGesturalNavPortrait.kt
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm.flicker.service.notification.flicker
+
+import android.tools.common.NavBar
+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.server.wm.flicker.service.notification.scenarios.OpenAppFromLockscreenNotificationCold
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class OpenAppFromLockscreenNotificationColdGesturalNavPortrait :
+    OpenAppFromLockscreenNotificationCold(NavBar.MODE_GESTURAL, Rotation.ROTATION_0) {
+    @ExpectedScenarios(["APP_LAUNCH_FROM_NOTIFICATION"])
+    @Test
+    override fun openAppFromLockscreenNotificationCold() =
+        super.openAppFromLockscreenNotificationCold()
+
+    companion object {
+        @JvmStatic
+        @FlickerConfigProvider
+        fun flickerConfigProvider(): FlickerConfig =
+            FlickerConfig().use(FlickerServiceConfig.DEFAULT)
+    }
+}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/flicker/OpenAppFromLockscreenNotificationWarm3ButtonNavLandscape.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/flicker/OpenAppFromLockscreenNotificationWarm3ButtonNavLandscape.kt
new file mode 100644
index 0000000..93200ba
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/flicker/OpenAppFromLockscreenNotificationWarm3ButtonNavLandscape.kt
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm.flicker.service.notification.flicker
+
+import android.tools.common.NavBar
+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.server.wm.flicker.service.notification.scenarios.OpenAppFromLockscreenNotificationWarm
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class OpenAppFromLockscreenNotificationWarm3ButtonNavLandscape :
+    OpenAppFromLockscreenNotificationWarm(NavBar.MODE_3BUTTON, Rotation.ROTATION_90) {
+    @ExpectedScenarios(["APP_LAUNCH_FROM_NOTIFICATION"])
+    @Test
+    override fun openAppFromLockscreenNotificationWarm() =
+        super.openAppFromLockscreenNotificationWarm()
+
+    companion object {
+        @JvmStatic
+        @FlickerConfigProvider
+        fun flickerConfigProvider(): FlickerConfig =
+            FlickerConfig().use(FlickerServiceConfig.DEFAULT)
+    }
+}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/flicker/OpenAppFromLockscreenNotificationWarm3ButtonNavPortrait.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/flicker/OpenAppFromLockscreenNotificationWarm3ButtonNavPortrait.kt
new file mode 100644
index 0000000..f5d41d2
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/flicker/OpenAppFromLockscreenNotificationWarm3ButtonNavPortrait.kt
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm.flicker.service.notification.flicker
+
+import android.tools.common.NavBar
+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.server.wm.flicker.service.notification.scenarios.OpenAppFromLockscreenNotificationWarm
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class OpenAppFromLockscreenNotificationWarm3ButtonNavPortrait :
+    OpenAppFromLockscreenNotificationWarm(NavBar.MODE_3BUTTON, Rotation.ROTATION_0) {
+    @ExpectedScenarios(["APP_LAUNCH_FROM_NOTIFICATION"])
+    @Test
+    override fun openAppFromLockscreenNotificationWarm() =
+        super.openAppFromLockscreenNotificationWarm()
+
+    companion object {
+        @JvmStatic
+        @FlickerConfigProvider
+        fun flickerConfigProvider(): FlickerConfig =
+            FlickerConfig().use(FlickerServiceConfig.DEFAULT)
+    }
+}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/flicker/OpenAppFromLockscreenNotificationWarmGesturalNavLandscape.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/flicker/OpenAppFromLockscreenNotificationWarmGesturalNavLandscape.kt
new file mode 100644
index 0000000..28f322b
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/flicker/OpenAppFromLockscreenNotificationWarmGesturalNavLandscape.kt
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm.flicker.service.notification.flicker
+
+import android.tools.common.NavBar
+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.server.wm.flicker.service.notification.scenarios.OpenAppFromLockscreenNotificationWarm
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class OpenAppFromLockscreenNotificationWarmGesturalNavLandscape :
+    OpenAppFromLockscreenNotificationWarm(NavBar.MODE_GESTURAL, Rotation.ROTATION_90) {
+    @ExpectedScenarios(["APP_LAUNCH_FROM_NOTIFICATION"])
+    @Test
+    override fun openAppFromLockscreenNotificationWarm() =
+        super.openAppFromLockscreenNotificationWarm()
+
+    companion object {
+        @JvmStatic
+        @FlickerConfigProvider
+        fun flickerConfigProvider(): FlickerConfig =
+            FlickerConfig().use(FlickerServiceConfig.DEFAULT)
+    }
+}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/flicker/OpenAppFromLockscreenNotificationWarmGesturalNavPortrait.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/flicker/OpenAppFromLockscreenNotificationWarmGesturalNavPortrait.kt
new file mode 100644
index 0000000..beb3fae
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/flicker/OpenAppFromLockscreenNotificationWarmGesturalNavPortrait.kt
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm.flicker.service.notification.flicker
+
+import android.tools.common.NavBar
+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.server.wm.flicker.service.notification.scenarios.OpenAppFromLockscreenNotificationWarm
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class OpenAppFromLockscreenNotificationWarmGesturalNavPortrait :
+    OpenAppFromLockscreenNotificationWarm(NavBar.MODE_GESTURAL, Rotation.ROTATION_0) {
+    @ExpectedScenarios(["APP_LAUNCH_FROM_NOTIFICATION"])
+    @Test
+    override fun openAppFromLockscreenNotificationWarm() =
+        super.openAppFromLockscreenNotificationWarm()
+
+    companion object {
+        @JvmStatic
+        @FlickerConfigProvider
+        fun flickerConfigProvider(): FlickerConfig =
+            FlickerConfig().use(FlickerServiceConfig.DEFAULT)
+    }
+}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/flicker/OpenAppFromLockscreenNotificationWithOverlayApp3ButtonNavLandscape.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/flicker/OpenAppFromLockscreenNotificationWithOverlayApp3ButtonNavLandscape.kt
new file mode 100644
index 0000000..b34da72
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/flicker/OpenAppFromLockscreenNotificationWithOverlayApp3ButtonNavLandscape.kt
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm.flicker.service.notification.flicker
+
+import android.tools.common.NavBar
+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.server.wm.flicker.service.notification.scenarios.OpenAppFromLockscreenNotificationWithOverlayApp
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class OpenAppFromLockscreenNotificationWithOverlayApp3ButtonNavLandscape :
+    OpenAppFromLockscreenNotificationWithOverlayApp(NavBar.MODE_3BUTTON, Rotation.ROTATION_90) {
+    @ExpectedScenarios(["APP_LAUNCH_FROM_NOTIFICATION"])
+    @Test
+    override fun openAppFromLockscreenNotificationWithOverlayApp() =
+        super.openAppFromLockscreenNotificationWithOverlayApp()
+
+    companion object {
+        @JvmStatic
+        @FlickerConfigProvider
+        fun flickerConfigProvider(): FlickerConfig =
+            FlickerConfig().use(FlickerServiceConfig.DEFAULT)
+    }
+}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/flicker/OpenAppFromLockscreenNotificationWithOverlayApp3ButtonNavPortrait.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/flicker/OpenAppFromLockscreenNotificationWithOverlayApp3ButtonNavPortrait.kt
new file mode 100644
index 0000000..b163897
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/flicker/OpenAppFromLockscreenNotificationWithOverlayApp3ButtonNavPortrait.kt
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm.flicker.service.notification.flicker
+
+import android.tools.common.NavBar
+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.server.wm.flicker.service.notification.scenarios.OpenAppFromLockscreenNotificationWithOverlayApp
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class OpenAppFromLockscreenNotificationWithOverlayApp3ButtonNavPortrait :
+    OpenAppFromLockscreenNotificationWithOverlayApp(NavBar.MODE_3BUTTON, Rotation.ROTATION_0) {
+    @ExpectedScenarios(["APP_LAUNCH_FROM_NOTIFICATION"])
+    @Test
+    override fun openAppFromLockscreenNotificationWithOverlayApp() =
+        super.openAppFromLockscreenNotificationWithOverlayApp()
+
+    companion object {
+        @JvmStatic
+        @FlickerConfigProvider
+        fun flickerConfigProvider(): FlickerConfig =
+            FlickerConfig().use(FlickerServiceConfig.DEFAULT)
+    }
+}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/flicker/OpenAppFromLockscreenNotificationWithOverlayAppGesturalNavLandscape.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/flicker/OpenAppFromLockscreenNotificationWithOverlayAppGesturalNavLandscape.kt
new file mode 100644
index 0000000..19b533e
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/flicker/OpenAppFromLockscreenNotificationWithOverlayAppGesturalNavLandscape.kt
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm.flicker.service.notification.flicker
+
+import android.tools.common.NavBar
+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.server.wm.flicker.service.notification.scenarios.OpenAppFromLockscreenNotificationWithOverlayApp
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class OpenAppFromLockscreenNotificationWithOverlayAppGesturalNavLandscape :
+    OpenAppFromLockscreenNotificationWithOverlayApp(NavBar.MODE_GESTURAL, Rotation.ROTATION_90) {
+    @ExpectedScenarios(["APP_LAUNCH_FROM_NOTIFICATION"])
+    @Test
+    override fun openAppFromLockscreenNotificationWithOverlayApp() =
+        super.openAppFromLockscreenNotificationWithOverlayApp()
+
+    companion object {
+        @JvmStatic
+        @FlickerConfigProvider
+        fun flickerConfigProvider(): FlickerConfig =
+            FlickerConfig().use(FlickerServiceConfig.DEFAULT)
+    }
+}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/flicker/OpenAppFromLockscreenNotificationWithOverlayAppGesturalNavPortrait.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/flicker/OpenAppFromLockscreenNotificationWithOverlayAppGesturalNavPortrait.kt
new file mode 100644
index 0000000..c9ed4f4
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/flicker/OpenAppFromLockscreenNotificationWithOverlayAppGesturalNavPortrait.kt
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm.flicker.service.notification.flicker
+
+import android.tools.common.NavBar
+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.server.wm.flicker.service.notification.scenarios.OpenAppFromLockscreenNotificationWithOverlayApp
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class OpenAppFromLockscreenNotificationWithOverlayAppGesturalNavPortrait :
+    OpenAppFromLockscreenNotificationWithOverlayApp(NavBar.MODE_GESTURAL, Rotation.ROTATION_0) {
+    @ExpectedScenarios(["APP_LAUNCH_FROM_NOTIFICATION"])
+    @Test
+    override fun openAppFromLockscreenNotificationWithOverlayApp() =
+        super.openAppFromLockscreenNotificationWithOverlayApp()
+
+    companion object {
+        @JvmStatic
+        @FlickerConfigProvider
+        fun flickerConfigProvider(): FlickerConfig =
+            FlickerConfig().use(FlickerServiceConfig.DEFAULT)
+    }
+}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/flicker/OpenAppFromNotificationCold3ButtonNavLandscape.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/flicker/OpenAppFromNotificationCold3ButtonNavLandscape.kt
new file mode 100644
index 0000000..866190f
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/flicker/OpenAppFromNotificationCold3ButtonNavLandscape.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.server.wm.flicker.service.notification.flicker
+
+import android.tools.common.NavBar
+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.server.wm.flicker.service.notification.scenarios.OpenAppFromNotificationCold
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class OpenAppFromNotificationCold3ButtonNavLandscape :
+    OpenAppFromNotificationCold(NavBar.MODE_3BUTTON, Rotation.ROTATION_90) {
+    @ExpectedScenarios(["APP_LAUNCH_FROM_NOTIFICATION"])
+    @Test
+    override fun openAppFromNotificationCold() = super.openAppFromNotificationCold()
+
+    companion object {
+        @JvmStatic
+        @FlickerConfigProvider
+        fun flickerConfigProvider(): FlickerConfig =
+            FlickerConfig().use(FlickerServiceConfig.DEFAULT)
+    }
+}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/flicker/OpenAppFromNotificationCold3ButtonNavPortrait.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/flicker/OpenAppFromNotificationCold3ButtonNavPortrait.kt
new file mode 100644
index 0000000..a8d6283
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/flicker/OpenAppFromNotificationCold3ButtonNavPortrait.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.server.wm.flicker.service.notification.flicker
+
+import android.tools.common.NavBar
+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.server.wm.flicker.service.notification.scenarios.OpenAppFromNotificationCold
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class OpenAppFromNotificationCold3ButtonNavPortrait :
+    OpenAppFromNotificationCold(NavBar.MODE_3BUTTON, Rotation.ROTATION_0) {
+    @ExpectedScenarios(["APP_LAUNCH_FROM_NOTIFICATION"])
+    @Test
+    override fun openAppFromNotificationCold() = super.openAppFromNotificationCold()
+
+    companion object {
+        @JvmStatic
+        @FlickerConfigProvider
+        fun flickerConfigProvider(): FlickerConfig =
+            FlickerConfig().use(FlickerServiceConfig.DEFAULT)
+    }
+}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/flicker/OpenAppFromNotificationColdGesturalNavLandscape.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/flicker/OpenAppFromNotificationColdGesturalNavLandscape.kt
new file mode 100644
index 0000000..ef84c3f
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/flicker/OpenAppFromNotificationColdGesturalNavLandscape.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.server.wm.flicker.service.notification.flicker
+
+import android.tools.common.NavBar
+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.server.wm.flicker.service.notification.scenarios.OpenAppFromNotificationCold
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class OpenAppFromNotificationColdGesturalNavLandscape :
+    OpenAppFromNotificationCold(NavBar.MODE_GESTURAL, Rotation.ROTATION_90) {
+    @ExpectedScenarios(["APP_LAUNCH_FROM_NOTIFICATION"])
+    @Test
+    override fun openAppFromNotificationCold() = super.openAppFromNotificationCold()
+
+    companion object {
+        @JvmStatic
+        @FlickerConfigProvider
+        fun flickerConfigProvider(): FlickerConfig =
+            FlickerConfig().use(FlickerServiceConfig.DEFAULT)
+    }
+}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/flicker/OpenAppFromNotificationColdGesturalNavPortrait.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/flicker/OpenAppFromNotificationColdGesturalNavPortrait.kt
new file mode 100644
index 0000000..5bb6b37
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/flicker/OpenAppFromNotificationColdGesturalNavPortrait.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.server.wm.flicker.service.notification.flicker
+
+import android.tools.common.NavBar
+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.server.wm.flicker.service.notification.scenarios.OpenAppFromNotificationCold
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class OpenAppFromNotificationColdGesturalNavPortrait :
+    OpenAppFromNotificationCold(NavBar.MODE_GESTURAL, Rotation.ROTATION_0) {
+    @ExpectedScenarios(["APP_LAUNCH_FROM_NOTIFICATION"])
+    @Test
+    override fun openAppFromNotificationCold() = super.openAppFromNotificationCold()
+
+    companion object {
+        @JvmStatic
+        @FlickerConfigProvider
+        fun flickerConfigProvider(): FlickerConfig =
+            FlickerConfig().use(FlickerServiceConfig.DEFAULT)
+    }
+}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/flicker/OpenAppFromNotificationWarm3ButtonNavLandscape.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/flicker/OpenAppFromNotificationWarm3ButtonNavLandscape.kt
new file mode 100644
index 0000000..39f25e9
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/flicker/OpenAppFromNotificationWarm3ButtonNavLandscape.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.server.wm.flicker.service.notification.flicker
+
+import android.tools.common.NavBar
+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.server.wm.flicker.service.notification.scenarios.OpenAppFromNotificationWarm
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class OpenAppFromNotificationWarm3ButtonNavLandscape :
+    OpenAppFromNotificationWarm(NavBar.MODE_3BUTTON, Rotation.ROTATION_90) {
+    @ExpectedScenarios(["APP_LAUNCH_FROM_NOTIFICATION"])
+    @Test
+    override fun openAppFromNotificationWarm() = super.openAppFromNotificationWarm()
+
+    companion object {
+        @JvmStatic
+        @FlickerConfigProvider
+        fun flickerConfigProvider(): FlickerConfig =
+            FlickerConfig().use(FlickerServiceConfig.DEFAULT)
+    }
+}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/flicker/OpenAppFromNotificationWarm3ButtonNavPortrait.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/flicker/OpenAppFromNotificationWarm3ButtonNavPortrait.kt
new file mode 100644
index 0000000..28816bc
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/flicker/OpenAppFromNotificationWarm3ButtonNavPortrait.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.server.wm.flicker.service.notification.flicker
+
+import android.tools.common.NavBar
+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.server.wm.flicker.service.notification.scenarios.OpenAppFromNotificationWarm
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class OpenAppFromNotificationWarm3ButtonNavPortrait :
+    OpenAppFromNotificationWarm(NavBar.MODE_3BUTTON, Rotation.ROTATION_0) {
+    @ExpectedScenarios(["APP_LAUNCH_FROM_NOTIFICATION"])
+    @Test
+    override fun openAppFromNotificationWarm() = super.openAppFromNotificationWarm()
+
+    companion object {
+        @JvmStatic
+        @FlickerConfigProvider
+        fun flickerConfigProvider(): FlickerConfig =
+            FlickerConfig().use(FlickerServiceConfig.DEFAULT)
+    }
+}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/flicker/OpenAppFromNotificationWarmGesturalNavLandscape.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/flicker/OpenAppFromNotificationWarmGesturalNavLandscape.kt
new file mode 100644
index 0000000..94ffe4e
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/flicker/OpenAppFromNotificationWarmGesturalNavLandscape.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.server.wm.flicker.service.notification.flicker
+
+import android.tools.common.NavBar
+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.server.wm.flicker.service.notification.scenarios.OpenAppFromNotificationWarm
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class OpenAppFromNotificationWarmGesturalNavLandscape :
+    OpenAppFromNotificationWarm(NavBar.MODE_GESTURAL, Rotation.ROTATION_90) {
+    @ExpectedScenarios(["APP_LAUNCH_FROM_NOTIFICATION"])
+    @Test
+    override fun openAppFromNotificationWarm() = super.openAppFromNotificationWarm()
+
+    companion object {
+        @JvmStatic
+        @FlickerConfigProvider
+        fun flickerConfigProvider(): FlickerConfig =
+            FlickerConfig().use(FlickerServiceConfig.DEFAULT)
+    }
+}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/flicker/OpenAppFromNotificationWarmGesturalNavPortrait.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/flicker/OpenAppFromNotificationWarmGesturalNavPortrait.kt
new file mode 100644
index 0000000..e5f5ec4
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/flicker/OpenAppFromNotificationWarmGesturalNavPortrait.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.server.wm.flicker.service.notification.flicker
+
+import android.tools.common.NavBar
+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.server.wm.flicker.service.notification.scenarios.OpenAppFromNotificationWarm
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class OpenAppFromNotificationWarmGesturalNavPortrait :
+    OpenAppFromNotificationWarm(NavBar.MODE_GESTURAL, Rotation.ROTATION_0) {
+    @ExpectedScenarios(["APP_LAUNCH_FROM_NOTIFICATION"])
+    @Test
+    override fun openAppFromNotificationWarm() = super.openAppFromNotificationWarm()
+
+    companion object {
+        @JvmStatic
+        @FlickerConfigProvider
+        fun flickerConfigProvider(): FlickerConfig =
+            FlickerConfig().use(FlickerServiceConfig.DEFAULT)
+    }
+}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/scenarios/NotificationUtils.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/scenarios/NotificationUtils.kt
new file mode 100644
index 0000000..ac4e169
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/scenarios/NotificationUtils.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.server.wm.flicker.service.notification.scenarios
+
+import android.app.Instrumentation
+import android.tools.device.traces.parsers.WindowManagerStateHelper
+import android.view.WindowInsets
+import android.view.WindowManager
+import androidx.test.platform.app.InstrumentationRegistry
+import androidx.test.uiautomator.By
+import androidx.test.uiautomator.UiDevice
+import androidx.test.uiautomator.Until
+import com.android.launcher3.tapl.LauncherInstrumentation
+
+object NotificationUtils {
+    private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
+    private val tapl = LauncherInstrumentation()
+    private val wmHelper = WindowManagerStateHelper(instrumentation)
+    private val device = UiDevice.getInstance(instrumentation)
+
+    fun openNotification(openingNotificationsFromLockScreen: Boolean) {
+        var startY = 10
+        var endY = 3 * device.displayHeight / 4
+        var steps = 25
+        if (openingNotificationsFromLockScreen) {
+            val wm: WindowManager =
+                instrumentation.context.getSystemService(WindowManager::class.java)
+                    ?: error("Unable to connect to WindowManager service")
+            val metricInsets = wm.currentWindowMetrics.windowInsets
+            val insets =
+                metricInsets.getInsetsIgnoringVisibility(
+                    WindowInsets.Type.statusBars() or WindowInsets.Type.displayCutout()
+                )
+
+            startY = insets.top + 100
+            endY = device.displayHeight / 2
+            steps = 4
+        }
+
+        // Swipe down to show the notification shade
+        val x = device.displayWidth / 2
+        device.swipe(x, startY, x, endY, steps)
+        device.waitForIdle(2000)
+        instrumentation.uiAutomation.syncInputTransactions()
+
+        // Launch the activity by clicking the notification
+        val notification =
+            device.wait(Until.findObject(By.text("Flicker Test Notification")), 2000L)
+        notification?.click() ?: error("Notification not found")
+        instrumentation.uiAutomation.syncInputTransactions()
+    }
+}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/scenarios/OpenAppFromLockscreenNotificationCold.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/scenarios/OpenAppFromLockscreenNotificationCold.kt
new file mode 100644
index 0000000..9c53ab3
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/scenarios/OpenAppFromLockscreenNotificationCold.kt
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm.flicker.service.notification.scenarios
+
+import android.app.Instrumentation
+import android.tools.common.NavBar
+import android.tools.common.Rotation
+import android.tools.device.helpers.wakeUpAndGoToHomeScreen
+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 com.android.server.wm.flicker.helpers.NotificationAppHelper
+import com.android.server.wm.flicker.service.Utils
+import org.junit.After
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+
+abstract class OpenAppFromLockscreenNotificationCold(
+    val gestureMode: NavBar = NavBar.MODE_GESTURAL,
+    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 testApp: NotificationAppHelper = NotificationAppHelper(instrumentation)
+
+    val openingNotificationsFromLockScreen = true
+
+    @Rule @JvmField val testSetupRule = Utils.testSetupRule(gestureMode, rotation)
+
+    @Before
+    fun setup() {
+        device.wakeUpAndGoToHomeScreen()
+        testApp.launchViaIntent(wmHelper)
+        wmHelper.StateSyncBuilder().withFullScreenApp(testApp).waitForAndVerify()
+        testApp.postNotification(wmHelper)
+        device.pressHome()
+        wmHelper.StateSyncBuilder().withHomeActivityVisible().waitForAndVerify()
+
+        // Close the app that posted the notification to trigger a cold start next time
+        // it is open - can't just kill it because that would remove the notification.
+        tapl.setExpectedRotationCheckEnabled(false)
+        tapl.goHome()
+        tapl.workspace.switchToOverview()
+        tapl.overview.dismissAllTasks()
+
+        device.sleep()
+        wmHelper.StateSyncBuilder().withoutTopVisibleAppWindows().waitForAndVerify()
+    }
+
+    @Test
+    open fun openAppFromLockscreenNotificationCold() {
+        device.wakeUp()
+
+        NotificationUtils.openNotification(openingNotificationsFromLockScreen)
+        // Wait for the app to launch
+        wmHelper.StateSyncBuilder().withFullScreenApp(testApp).waitForAndVerify()
+    }
+
+    @After
+    fun teardown() {
+        testApp.exit(wmHelper)
+    }
+}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/scenarios/OpenAppFromLockscreenNotificationWarm.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/scenarios/OpenAppFromLockscreenNotificationWarm.kt
new file mode 100644
index 0000000..31f55d9
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/scenarios/OpenAppFromLockscreenNotificationWarm.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.server.wm.flicker.service.notification.scenarios
+
+import android.app.Instrumentation
+import android.tools.common.NavBar
+import android.tools.common.Rotation
+import android.tools.device.helpers.wakeUpAndGoToHomeScreen
+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 com.android.server.wm.flicker.helpers.NotificationAppHelper
+import com.android.server.wm.flicker.service.Utils
+import org.junit.After
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+
+abstract class OpenAppFromLockscreenNotificationWarm(
+    val gestureMode: NavBar = NavBar.MODE_GESTURAL,
+    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 testApp: NotificationAppHelper = NotificationAppHelper(instrumentation)
+
+    private val openingNotificationsFromLockScreen = true
+
+    @Rule @JvmField val testSetupRule = Utils.testSetupRule(gestureMode, rotation)
+
+    @Before
+    fun setup() {
+        device.wakeUpAndGoToHomeScreen()
+        testApp.launchViaIntent(wmHelper)
+        wmHelper.StateSyncBuilder().withFullScreenApp(testApp).waitForAndVerify()
+        testApp.postNotification(wmHelper)
+        device.pressHome()
+        wmHelper.StateSyncBuilder().withHomeActivityVisible().waitForAndVerify()
+
+        device.sleep()
+        wmHelper.StateSyncBuilder().withoutTopVisibleAppWindows().waitForAndVerify()
+    }
+
+    @Test
+    open fun openAppFromLockscreenNotificationWarm() {
+        device.wakeUp()
+
+        NotificationUtils.openNotification(openingNotificationsFromLockScreen)
+        // Wait for the app to launch
+        wmHelper.StateSyncBuilder().withFullScreenApp(testApp).waitForAndVerify()
+    }
+
+    @After
+    fun teardown() {
+        testApp.exit(wmHelper)
+    }
+}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/scenarios/OpenAppFromLockscreenNotificationWithOverlayApp.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/scenarios/OpenAppFromLockscreenNotificationWithOverlayApp.kt
new file mode 100644
index 0000000..b971555
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/scenarios/OpenAppFromLockscreenNotificationWithOverlayApp.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.server.wm.flicker.service.notification.scenarios
+
+import android.app.Instrumentation
+import android.tools.common.NavBar
+import android.tools.common.Rotation
+import android.tools.device.helpers.wakeUpAndGoToHomeScreen
+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 com.android.server.wm.flicker.helpers.NotificationAppHelper
+import com.android.server.wm.flicker.helpers.ShowWhenLockedAppHelper
+import com.android.server.wm.flicker.service.Utils
+import org.junit.After
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+
+abstract class OpenAppFromLockscreenNotificationWithOverlayApp(
+    val gestureMode: NavBar = NavBar.MODE_GESTURAL,
+    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 showWhenLockedApp = ShowWhenLockedAppHelper(instrumentation)
+    private val testApp: NotificationAppHelper = NotificationAppHelper(instrumentation)
+
+    // Although we are technically still locked here, the overlay app means we should open the
+    // notification shade as if we were unlocked.
+    private val openingNotificationsFromLockScreen = false
+
+    @Rule @JvmField val testSetupRule = Utils.testSetupRule(gestureMode, rotation)
+
+    @Before
+    fun setup() {
+        device.wakeUpAndGoToHomeScreen()
+        testApp.launchViaIntent(wmHelper)
+        wmHelper.StateSyncBuilder().withFullScreenApp(testApp).waitForAndVerify()
+        testApp.postNotification(wmHelper)
+        device.pressHome()
+        wmHelper.StateSyncBuilder().withHomeActivityVisible().waitForAndVerify()
+
+        // Close the app that posted the notification to trigger a cold start next time
+        // it is open - can't just kill it because that would remove the notification.
+        tapl.setExpectedRotationCheckEnabled(false)
+        tapl.goHome()
+        tapl.workspace.switchToOverview()
+        tapl.overview.dismissAllTasks()
+
+        device.sleep()
+        wmHelper.StateSyncBuilder().withoutTopVisibleAppWindows().waitForAndVerify()
+
+        device.wakeUpAndGoToHomeScreen()
+
+        // Launch an activity that is shown when the device is locked
+        showWhenLockedApp.launchViaIntent(wmHelper)
+        wmHelper.StateSyncBuilder().withFullScreenApp(showWhenLockedApp).waitForAndVerify()
+
+        device.sleep()
+        wmHelper.StateSyncBuilder().withoutTopVisibleAppWindows().waitForAndVerify()
+    }
+
+    @Test
+    open fun openAppFromLockscreenNotificationWithOverlayApp() {
+        device.wakeUp()
+        NotificationUtils.openNotification(openingNotificationsFromLockScreen)
+        // Wait for the app to launch
+        wmHelper.StateSyncBuilder().withFullScreenApp(testApp).waitForAndVerify()
+    }
+
+    @After
+    fun teardown() {
+        testApp.exit(wmHelper)
+        showWhenLockedApp.exit(wmHelper)
+    }
+}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/scenarios/OpenAppFromNotificationCold.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/scenarios/OpenAppFromNotificationCold.kt
new file mode 100644
index 0000000..fed077e
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/scenarios/OpenAppFromNotificationCold.kt
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm.flicker.service.notification.scenarios
+
+import android.app.Instrumentation
+import android.tools.common.NavBar
+import android.tools.common.Rotation
+import android.tools.device.helpers.wakeUpAndGoToHomeScreen
+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 com.android.server.wm.flicker.helpers.NotificationAppHelper
+import com.android.server.wm.flicker.service.Utils
+import org.junit.After
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+
+abstract class OpenAppFromNotificationCold(
+    val gestureMode: NavBar = NavBar.MODE_GESTURAL,
+    val rotation: Rotation = Rotation.ROTATION_0
+) {
+    private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
+    private val tapl = LauncherInstrumentation()
+    private val device = UiDevice.getInstance(instrumentation)
+    private val wmHelper = WindowManagerStateHelper(instrumentation)
+    private val testApp: NotificationAppHelper = NotificationAppHelper(instrumentation)
+
+    @Rule @JvmField val testSetupRule = Utils.testSetupRule(gestureMode, rotation)
+
+    private val openingNotificationsFromLockScreen = false
+
+    @Before
+    fun setup() {
+        device.wakeUpAndGoToHomeScreen()
+        testApp.launchViaIntent(wmHelper)
+        wmHelper.StateSyncBuilder().withFullScreenApp(testApp).waitForAndVerify()
+        testApp.postNotification(wmHelper)
+        device.pressHome()
+        wmHelper.StateSyncBuilder().withHomeActivityVisible().waitForAndVerify()
+
+        // Close the app that posted the notification to trigger a cold start next time
+        // it is open - can't just kill it because that would remove the notification.
+        tapl.setExpectedRotationCheckEnabled(false)
+        tapl.goHome()
+        tapl.workspace.switchToOverview()
+        tapl.overview.dismissAllTasks()
+    }
+
+    @Test
+    open fun openAppFromNotificationCold() {
+        NotificationUtils.openNotification(openingNotificationsFromLockScreen)
+        // Wait for the app to launch
+        wmHelper.StateSyncBuilder().withFullScreenApp(testApp).waitForAndVerify()
+    }
+
+    @After
+    fun teardown() {
+        testApp.exit(wmHelper)
+    }
+}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/scenarios/OpenAppFromNotificationWarm.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/scenarios/OpenAppFromNotificationWarm.kt
new file mode 100644
index 0000000..403790e
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/scenarios/OpenAppFromNotificationWarm.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.server.wm.flicker.service.notification.scenarios
+
+import android.app.Instrumentation
+import android.tools.common.NavBar
+import android.tools.common.Rotation
+import android.tools.device.helpers.wakeUpAndGoToHomeScreen
+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 com.android.server.wm.flicker.helpers.NotificationAppHelper
+import com.android.server.wm.flicker.service.Utils
+import org.junit.After
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+
+abstract class OpenAppFromNotificationWarm(
+    val gestureMode: NavBar = NavBar.MODE_GESTURAL,
+    val rotation: Rotation = Rotation.ROTATION_0
+) {
+    private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
+    private val tapl = LauncherInstrumentation()
+    private val device = UiDevice.getInstance(instrumentation)
+    private val wmHelper = WindowManagerStateHelper(instrumentation)
+    private val testApp: NotificationAppHelper = NotificationAppHelper(instrumentation)
+
+    private val openingNotificationsFromLockScreen = false
+
+    @Rule @JvmField val testSetupRule = Utils.testSetupRule(gestureMode, rotation)
+
+    @Before
+    fun setup() {
+        device.wakeUpAndGoToHomeScreen()
+        testApp.launchViaIntent(wmHelper)
+        wmHelper.StateSyncBuilder().withFullScreenApp(testApp).waitForAndVerify()
+        testApp.postNotification(wmHelper)
+        device.pressHome()
+        wmHelper.StateSyncBuilder().withHomeActivityVisible().waitForAndVerify()
+    }
+
+    @Test
+    open fun openAppFromNotificationWarm() {
+        NotificationUtils.openNotification(openingNotificationsFromLockScreen)
+        // Wait for the app to launch
+        wmHelper.StateSyncBuilder().withFullScreenApp(testApp).waitForAndVerify()
+    }
+
+    @After
+    fun teardown() {
+        testApp.exit(wmHelper)
+    }
+}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/service/quickswitch/flicker/QuickSwitchBetweenTwoAppsBackGesturalNavLandscape.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/service/quickswitch/flicker/QuickSwitchBetweenTwoAppsBackGesturalNavLandscape.kt
new file mode 100644
index 0000000..d611a42
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/service/quickswitch/flicker/QuickSwitchBetweenTwoAppsBackGesturalNavLandscape.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.server.wm.flicker.service.quickswitch.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.server.wm.flicker.service.quickswitch.scenarios.QuickSwitchBetweenTwoAppsBack
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class QuickSwitchBetweenTwoAppsBackGesturalNavLandscape :
+    QuickSwitchBetweenTwoAppsBack(Rotation.ROTATION_90) {
+    @ExpectedScenarios(["QUICKSWITCH"])
+    @Test
+    override fun quickSwitchBetweenTwoAppsBack() = super.quickSwitchBetweenTwoAppsBack()
+
+    companion object {
+        @JvmStatic
+        @FlickerConfigProvider
+        fun flickerConfigProvider(): FlickerConfig =
+            FlickerConfig().use(FlickerServiceConfig.DEFAULT)
+    }
+}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/service/quickswitch/flicker/QuickSwitchBetweenTwoAppsBackGesturalNavPortrait.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/service/quickswitch/flicker/QuickSwitchBetweenTwoAppsBackGesturalNavPortrait.kt
new file mode 100644
index 0000000..e6bcbba
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/service/quickswitch/flicker/QuickSwitchBetweenTwoAppsBackGesturalNavPortrait.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.server.wm.flicker.service.quickswitch.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.server.wm.flicker.service.quickswitch.scenarios.QuickSwitchBetweenTwoAppsBack
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class QuickSwitchBetweenTwoAppsBackGesturalNavPortrait :
+    QuickSwitchBetweenTwoAppsBack(Rotation.ROTATION_0) {
+    @ExpectedScenarios(["QUICKSWITCH"])
+    @Test
+    override fun quickSwitchBetweenTwoAppsBack() = super.quickSwitchBetweenTwoAppsBack()
+
+    companion object {
+        @JvmStatic
+        @FlickerConfigProvider
+        fun flickerConfigProvider(): FlickerConfig =
+            FlickerConfig().use(FlickerServiceConfig.DEFAULT)
+    }
+}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/service/quickswitch/flicker/QuickSwitchBetweenTwoAppsForwardGesturalNavLandscape.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/service/quickswitch/flicker/QuickSwitchBetweenTwoAppsForwardGesturalNavLandscape.kt
new file mode 100644
index 0000000..2a26d63
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/service/quickswitch/flicker/QuickSwitchBetweenTwoAppsForwardGesturalNavLandscape.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.server.wm.flicker.service.quickswitch.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.server.wm.flicker.service.quickswitch.scenarios.QuickSwitchBetweenTwoAppsForward
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class QuickSwitchBetweenTwoAppsForwardGesturalNavLandscape :
+    QuickSwitchBetweenTwoAppsForward(Rotation.ROTATION_90) {
+    @ExpectedScenarios(["QUICKSWITCH"])
+    @Test
+    override fun quickSwitchBetweenTwoAppsForward() = super.quickSwitchBetweenTwoAppsForward()
+
+    companion object {
+        @JvmStatic
+        @FlickerConfigProvider
+        fun flickerConfigProvider(): FlickerConfig =
+            FlickerConfig().use(FlickerServiceConfig.DEFAULT)
+    }
+}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/service/quickswitch/flicker/QuickSwitchBetweenTwoAppsForwardGesturalNavPortrait.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/service/quickswitch/flicker/QuickSwitchBetweenTwoAppsForwardGesturalNavPortrait.kt
new file mode 100644
index 0000000..5ce7143
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/service/quickswitch/flicker/QuickSwitchBetweenTwoAppsForwardGesturalNavPortrait.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.server.wm.flicker.service.quickswitch.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.server.wm.flicker.service.quickswitch.scenarios.QuickSwitchBetweenTwoAppsForward
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class QuickSwitchBetweenTwoAppsForwardGesturalNavPortrait :
+    QuickSwitchBetweenTwoAppsForward(Rotation.ROTATION_0) {
+    @ExpectedScenarios(["QUICKSWITCH"])
+    @Test
+    override fun quickSwitchBetweenTwoAppsForward() = super.quickSwitchBetweenTwoAppsForward()
+
+    companion object {
+        @JvmStatic
+        @FlickerConfigProvider
+        fun flickerConfigProvider(): FlickerConfig =
+            FlickerConfig().use(FlickerServiceConfig.DEFAULT)
+    }
+}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/service/quickswitch/flicker/QuickSwitchFromLauncherGesturalNavLandscape.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/service/quickswitch/flicker/QuickSwitchFromLauncherGesturalNavLandscape.kt
new file mode 100644
index 0000000..cff47bb
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/service/quickswitch/flicker/QuickSwitchFromLauncherGesturalNavLandscape.kt
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm.flicker.service.quickswitch.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.server.wm.flicker.service.quickswitch.scenarios.QuickSwitchFromLauncher
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class QuickSwitchFromLauncherGesturalNavLandscape : QuickSwitchFromLauncher(Rotation.ROTATION_90) {
+    @ExpectedScenarios(["QUICKSWITCH"])
+    @Test
+    override fun quickSwitchFromLauncher() = super.quickSwitchFromLauncher()
+
+    companion object {
+        @JvmStatic
+        @FlickerConfigProvider
+        fun flickerConfigProvider(): FlickerConfig =
+            FlickerConfig().use(FlickerServiceConfig.DEFAULT)
+    }
+}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/service/quickswitch/flicker/QuickSwitchFromLauncherGesturalNavPortrait.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/service/quickswitch/flicker/QuickSwitchFromLauncherGesturalNavPortrait.kt
new file mode 100644
index 0000000..33095a6
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/service/quickswitch/flicker/QuickSwitchFromLauncherGesturalNavPortrait.kt
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm.flicker.service.quickswitch.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.server.wm.flicker.service.quickswitch.scenarios.QuickSwitchFromLauncher
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class QuickSwitchFromLauncherGesturalNavPortrait : QuickSwitchFromLauncher(Rotation.ROTATION_0) {
+    @ExpectedScenarios(["QUICKSWITCH"])
+    @Test
+    override fun quickSwitchFromLauncher() = super.quickSwitchFromLauncher()
+
+    companion object {
+        @JvmStatic
+        @FlickerConfigProvider
+        fun flickerConfigProvider(): FlickerConfig =
+            FlickerConfig().use(FlickerServiceConfig.DEFAULT)
+    }
+}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/service/quickswitch/scenarios/QuickSwitchBetweenTwoAppsBack.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/service/quickswitch/scenarios/QuickSwitchBetweenTwoAppsBack.kt
new file mode 100644
index 0000000..93130c4
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/service/quickswitch/scenarios/QuickSwitchBetweenTwoAppsBack.kt
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm.flicker.service.quickswitch.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 com.android.launcher3.tapl.LauncherInstrumentation
+import com.android.server.wm.flicker.helpers.NonResizeableAppHelper
+import com.android.server.wm.flicker.helpers.SimpleAppHelper
+import com.android.server.wm.flicker.service.Utils
+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 QuickSwitchBetweenTwoAppsBack(val rotation: Rotation = Rotation.ROTATION_0) {
+    private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
+    private val tapl = LauncherInstrumentation()
+    private val wmHelper = WindowManagerStateHelper(instrumentation)
+    private val testApp1 = SimpleAppHelper(instrumentation)
+    private val testApp2 = NonResizeableAppHelper(instrumentation)
+
+    @Rule @JvmField val testSetupRule = Utils.testSetupRule(NavBar.MODE_GESTURAL, rotation)
+
+    @Before
+    fun setup() {
+        tapl.setExpectedRotation(rotation.value)
+        tapl.setIgnoreTaskbarVisibility(true)
+        testApp1.launchViaIntent(wmHelper)
+        testApp2.launchViaIntent(wmHelper)
+    }
+
+    @Test
+    open fun quickSwitchBetweenTwoAppsBack() {
+        tapl.launchedAppState.quickSwitchToPreviousApp()
+        wmHelper
+            .StateSyncBuilder()
+            .withFullScreenApp(testApp1)
+            .withNavOrTaskBarVisible()
+            .withStatusBarVisible()
+            .waitForAndVerify()
+    }
+
+    @After
+    fun teardown() {
+        testApp1.exit(wmHelper)
+        testApp2.exit(wmHelper)
+    }
+}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/service/quickswitch/scenarios/QuickSwitchBetweenTwoAppsForward.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/service/quickswitch/scenarios/QuickSwitchBetweenTwoAppsForward.kt
new file mode 100644
index 0000000..4941eea
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/service/quickswitch/scenarios/QuickSwitchBetweenTwoAppsForward.kt
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm.flicker.service.quickswitch.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 com.android.launcher3.tapl.LauncherInstrumentation
+import com.android.server.wm.flicker.helpers.NonResizeableAppHelper
+import com.android.server.wm.flicker.helpers.SimpleAppHelper
+import com.android.server.wm.flicker.service.Utils
+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 QuickSwitchBetweenTwoAppsForward(val rotation: Rotation = Rotation.ROTATION_0) {
+    private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
+    private val tapl = LauncherInstrumentation()
+    private val wmHelper = WindowManagerStateHelper(instrumentation)
+    private val testApp1 = SimpleAppHelper(instrumentation)
+    private val testApp2 = NonResizeableAppHelper(instrumentation)
+
+    @Rule @JvmField val testSetupRule = Utils.testSetupRule(NavBar.MODE_GESTURAL, rotation)
+
+    @Before
+    fun setup() {
+        tapl.setExpectedRotation(rotation.value)
+
+        testApp1.launchViaIntent(wmHelper)
+        testApp2.launchViaIntent(wmHelper)
+        tapl.launchedAppState.quickSwitchToPreviousApp()
+        wmHelper
+            .StateSyncBuilder()
+            .withFullScreenApp(testApp1)
+            .withNavOrTaskBarVisible()
+            .withStatusBarVisible()
+            .waitForAndVerify()
+    }
+
+    @Test
+    open fun quickSwitchBetweenTwoAppsForward() {
+        tapl.launchedAppState.quickSwitchToPreviousAppSwipeLeft()
+        wmHelper
+            .StateSyncBuilder()
+            .withFullScreenApp(testApp2)
+            .withNavOrTaskBarVisible()
+            .withStatusBarVisible()
+            .waitForAndVerify()
+    }
+
+    @After
+    fun teardown() {
+        testApp1.exit(wmHelper)
+        testApp2.exit(wmHelper)
+    }
+}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/service/quickswitch/scenarios/QuickSwitchFromLauncher.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/service/quickswitch/scenarios/QuickSwitchFromLauncher.kt
new file mode 100644
index 0000000..7afe526
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/service/quickswitch/scenarios/QuickSwitchFromLauncher.kt
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm.flicker.service.quickswitch.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 com.android.launcher3.tapl.LauncherInstrumentation
+import com.android.server.wm.flicker.helpers.SimpleAppHelper
+import com.android.server.wm.flicker.service.Utils
+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 QuickSwitchFromLauncher(val rotation: Rotation = Rotation.ROTATION_0) {
+    private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
+    private val tapl = LauncherInstrumentation()
+    private val wmHelper = WindowManagerStateHelper(instrumentation)
+    private val testApp = SimpleAppHelper(instrumentation)
+
+    @Rule @JvmField val testSetupRule = Utils.testSetupRule(NavBar.MODE_GESTURAL, rotation)
+
+    @Before
+    fun setup() {
+        tapl.setExpectedRotationCheckEnabled(false)
+
+        tapl.setExpectedRotation(rotation.value)
+
+        testApp.launchViaIntent(wmHelper)
+        tapl.goHome()
+        wmHelper
+            .StateSyncBuilder()
+            .withHomeActivityVisible()
+            .withWindowSurfaceDisappeared(testApp)
+            .waitForAndVerify()
+    }
+
+    @Test
+    open fun quickSwitchFromLauncher() {
+        tapl.workspace.quickSwitchToPreviousApp()
+        wmHelper
+            .StateSyncBuilder()
+            .withFullScreenApp(testApp)
+            .withNavOrTaskBarVisible()
+            .withStatusBarVisible()
+            .waitForAndVerify()
+    }
+
+    @After
+    fun teardown() {
+        testApp.exit(wmHelper)
+    }
+}
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();
     }
 }