Merge "Update recent tasks when task moves to top" into udc-qpr-dev
diff --git a/apct-tests/perftests/multiuser/src/android/multiuser/BenchmarkRunner.java b/apct-tests/perftests/multiuser/src/android/multiuser/BenchmarkRunner.java
index a9f720a..515ddc8 100644
--- a/apct-tests/perftests/multiuser/src/android/multiuser/BenchmarkRunner.java
+++ b/apct-tests/perftests/multiuser/src/android/multiuser/BenchmarkRunner.java
@@ -80,7 +80,7 @@
 
     private void prepareForNextRun() {
         SystemClock.sleep(COOL_OFF_PERIOD_MS);
-        ShellHelper.runShellCommand("am wait-for-broadcast-idle");
+        ShellHelper.runShellCommand("am wait-for-broadcast-idle --flush-broadcast-loopers");
         mStartTimeNs = System.nanoTime();
         mPausedDurationNs = 0;
     }
@@ -102,7 +102,7 @@
      * to avoid unnecessary waiting.
      */
     public void resumeTiming() {
-        ShellHelper.runShellCommand("am wait-for-broadcast-idle");
+        ShellHelper.runShellCommand("am wait-for-broadcast-idle --flush-broadcast-loopers");
         resumeTimer();
     }
 
diff --git a/apct-tests/perftests/multiuser/src/android/multiuser/UserLifecycleTests.java b/apct-tests/perftests/multiuser/src/android/multiuser/UserLifecycleTests.java
index 19a4766..6dba5b3 100644
--- a/apct-tests/perftests/multiuser/src/android/multiuser/UserLifecycleTests.java
+++ b/apct-tests/perftests/multiuser/src/android/multiuser/UserLifecycleTests.java
@@ -1541,7 +1541,8 @@
 
     private void waitForBroadcastIdle() {
         try {
-            ShellHelper.runShellCommandWithTimeout("am wait-for-broadcast-idle", TIMEOUT_IN_SECOND);
+            ShellHelper.runShellCommandWithTimeout(
+                    "am wait-for-broadcast-idle --flush-broadcast-loopers", TIMEOUT_IN_SECOND);
         } catch (TimeoutException e) {
             Log.e(TAG, "Ending waitForBroadcastIdle because it is taking too long", e);
         }
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index df9257c..5c1b3ee 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -5006,12 +5006,6 @@
             return mUserExtras;
         }
 
-        private Bundle getAllExtras() {
-            final Bundle saveExtras = (Bundle) mUserExtras.clone();
-            saveExtras.putAll(mN.extras);
-            return saveExtras;
-        }
-
         /**
          * Add an action to this notification. Actions are typically displayed by
          * the system as a button adjacent to the notification content.
@@ -6617,9 +6611,16 @@
                                 + " vs bubble: " + mN.mBubbleMetadata.getShortcutId());
             }
 
-            // first, add any extras from the calling code
+            // Adds any new extras provided by the user.
             if (mUserExtras != null) {
-                mN.extras = getAllExtras();
+                final Bundle saveExtras = (Bundle) mUserExtras.clone();
+                if (SystemProperties.getBoolean(
+                        "persist.sysui.notification.builder_extras_override", false)) {
+                    mN.extras.putAll(saveExtras);
+                } else {
+                    saveExtras.putAll(mN.extras);
+                    mN.extras = saveExtras;
+                }
             }
 
             mN.creationTime = System.currentTimeMillis();
diff --git a/core/java/android/app/StatusBarManager.java b/core/java/android/app/StatusBarManager.java
index 776e34b..385fd50 100644
--- a/core/java/android/app/StatusBarManager.java
+++ b/core/java/android/app/StatusBarManager.java
@@ -24,9 +24,11 @@
 import android.annotation.SystemApi;
 import android.annotation.SystemService;
 import android.annotation.TestApi;
+import android.annotation.UserIdInt;
 import android.app.compat.CompatChanges;
 import android.compat.annotation.ChangeId;
 import android.compat.annotation.EnabledSince;
+import android.compat.annotation.LoggingOnly;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.content.ComponentName;
 import android.content.Context;
@@ -49,6 +51,7 @@
 import android.view.KeyEvent;
 import android.view.View;
 
+import com.android.internal.compat.IPlatformCompat;
 import com.android.internal.statusbar.AppClipsServiceConnector;
 import com.android.internal.statusbar.IAddTileResultCallback;
 import com.android.internal.statusbar.IStatusBarService;
@@ -170,6 +173,8 @@
     public @interface Disable2Flags {}
     // LINT.ThenChange(frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/disableflags/DisableFlagsLogger.kt)
 
+    private static final String TAG = "StatusBarManager";
+
     /**
      * Default disable flags for setup
      *
@@ -572,13 +577,13 @@
     private static final long MEDIA_CONTROL_SESSION_ACTIONS = 203800354L;
 
     /**
-     * Media controls based on {@link android.app.Notification.MediaStyle} notifications will be
-     * required to include a non-empty title, either in the {@link android.media.MediaMetadata} or
+     * Media controls based on {@link android.app.Notification.MediaStyle} notifications should
+     * include a non-empty title, either in the {@link android.media.MediaMetadata} or
      * notification title.
      */
     @ChangeId
-    @EnabledSince(targetSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
-    private static final long MEDIA_CONTROL_REQUIRES_TITLE = 274775190L;
+    @LoggingOnly
+    private static final long MEDIA_CONTROL_BLANK_TITLE = 274775190L;
 
     @UnsupportedAppUsage
     private Context mContext;
@@ -586,6 +591,9 @@
     @UnsupportedAppUsage
     private IBinder mToken = new Binder();
 
+    private final IPlatformCompat mPlatformCompat = IPlatformCompat.Stub.asInterface(
+            ServiceManager.getService(Context.PLATFORM_COMPAT_SERVICE));
+
     @UnsupportedAppUsage
     StatusBarManager(Context context) {
         mContext = context;
@@ -597,7 +605,7 @@
             mService = IStatusBarService.Stub.asInterface(
                     ServiceManager.getService(Context.STATUS_BAR_SERVICE));
             if (mService == null) {
-                Slog.w("StatusBarManager", "warning: no STATUS_BAR_SERVICE");
+                Slog.w(TAG, "warning: no STATUS_BAR_SERVICE");
             }
         }
         return mService;
@@ -1226,18 +1234,22 @@
     }
 
     /**
-     * Checks whether the given package must include a non-empty title for its media controls.
+     * Log that the given package has posted media controls with a blank title
      *
      * @param packageName App posting media controls
-     * @param user Current user handle
-     * @return true if the app is required to provide a non-empty title
+     * @param userId Current user ID
+     * @throws RuntimeException if there is an error reporting the change
      *
      * @hide
      */
-    @RequiresPermission(allOf = {android.Manifest.permission.READ_COMPAT_CHANGE_CONFIG,
-            android.Manifest.permission.LOG_COMPAT_CHANGE})
-    public static boolean isMediaTitleRequiredForApp(String packageName, UserHandle user) {
-        return CompatChanges.isChangeEnabled(MEDIA_CONTROL_REQUIRES_TITLE, packageName, user);
+    public void logBlankMediaTitle(String packageName, @UserIdInt int userId)
+            throws RuntimeException {
+        try {
+            mPlatformCompat.reportChangeByPackageName(MEDIA_CONTROL_BLANK_TITLE, packageName,
+                        userId);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
     }
 
     /**
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index 7f19897..8fafb18 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -2883,6 +2883,20 @@
             "android.software.car.templates_host";
 
     /**
+     * Feature for {@link #getSystemAvailableFeatures} and {@link #hasSystemFeature}:If this
+     * feature is supported, the device should also declare {@link #FEATURE_AUTOMOTIVE} and show
+     * a UI that can display multiple tasks at the same time on a single display. The user can
+     * perform multiple actions on different tasks simultaneously. Apps open in split screen mode
+     * by default, instead of full screen. Unlike Android's multi-window mode, where users can
+     * choose how to display apps, the device determines how apps are shown.
+     *
+     * @hide
+     */
+    @SdkConstant(SdkConstantType.FEATURE)
+    public static final String FEATURE_CAR_SPLITSCREEN_MULTITASKING =
+            "android.software.car.splitscreen_multitasking";
+
+    /**
      * Feature for {@link #getSystemAvailableFeatures} and
      * {@link #hasSystemFeature(String, int)}: If this feature is supported, the device supports
      * {@link android.security.identity.IdentityCredentialStore} implemented in secure hardware
diff --git a/core/java/android/inputmethodservice/NavigationBarController.java b/core/java/android/inputmethodservice/NavigationBarController.java
index 6910501..78388ef 100644
--- a/core/java/android/inputmethodservice/NavigationBarController.java
+++ b/core/java/android/inputmethodservice/NavigationBarController.java
@@ -246,8 +246,7 @@
         @Override
         public void updateTouchableInsets(@NonNull InputMethodService.Insets originalInsets,
                 @NonNull ViewTreeObserver.InternalInsetsInfo dest) {
-            if (!mImeDrawsImeNavBar || mNavigationBarFrame == null
-                    || mService.isExtractViewShown()) {
+            if (!mImeDrawsImeNavBar || mNavigationBarFrame == null) {
                 return;
             }
 
@@ -255,53 +254,58 @@
             if (systemInsets != null) {
                 final Window window = mService.mWindow.getWindow();
                 final View decor = window.getDecorView();
-                Region touchableRegion = null;
-                final View inputFrame = mService.mInputFrame;
-                switch (originalInsets.touchableInsets) {
-                    case ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_FRAME:
-                        if (inputFrame.getVisibility() == View.VISIBLE) {
-                            inputFrame.getLocationInWindow(mTempPos);
-                            mTempRect.set(mTempPos[0], mTempPos[1],
-                                    mTempPos[0] + inputFrame.getWidth(),
-                                    mTempPos[1] + inputFrame.getHeight());
-                            touchableRegion = new Region(mTempRect);
-                        }
-                        break;
-                    case ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_CONTENT:
-                        if (inputFrame.getVisibility() == View.VISIBLE) {
-                            inputFrame.getLocationInWindow(mTempPos);
-                            mTempRect.set(mTempPos[0], originalInsets.contentTopInsets,
-                                    mTempPos[0] + inputFrame.getWidth() ,
-                                    mTempPos[1] + inputFrame.getHeight());
-                            touchableRegion = new Region(mTempRect);
-                        }
-                        break;
-                    case ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_VISIBLE:
-                        if (inputFrame.getVisibility() == View.VISIBLE) {
-                            inputFrame.getLocationInWindow(mTempPos);
-                            mTempRect.set(mTempPos[0], originalInsets.visibleTopInsets,
-                                    mTempPos[0] + inputFrame.getWidth(),
-                                    mTempPos[1] + inputFrame.getHeight());
-                            touchableRegion = new Region(mTempRect);
-                        }
-                        break;
-                    case ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_REGION:
-                        touchableRegion = new Region();
-                        touchableRegion.set(originalInsets.touchableRegion);
-                        break;
-                }
-                // Hereafter "mTempRect" means a navigation bar rect.
-                mTempRect.set(decor.getLeft(), decor.getBottom() - systemInsets.bottom,
-                        decor.getRight(), decor.getBottom());
-                if (touchableRegion == null) {
-                    touchableRegion = new Region(mTempRect);
-                } else {
-                    touchableRegion.union(mTempRect);
-                }
 
-                dest.touchableRegion.set(touchableRegion);
-                dest.setTouchableInsets(
-                        ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_REGION);
+                // If the extract view is shown, everything is touchable, so no need to update
+                // touchable insets, but we still update normal insets below.
+                if (!mService.isExtractViewShown()) {
+                    Region touchableRegion = null;
+                    final View inputFrame = mService.mInputFrame;
+                    switch (originalInsets.touchableInsets) {
+                        case ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_FRAME:
+                            if (inputFrame.getVisibility() == View.VISIBLE) {
+                                inputFrame.getLocationInWindow(mTempPos);
+                                mTempRect.set(mTempPos[0], mTempPos[1],
+                                        mTempPos[0] + inputFrame.getWidth(),
+                                        mTempPos[1] + inputFrame.getHeight());
+                                touchableRegion = new Region(mTempRect);
+                            }
+                            break;
+                        case ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_CONTENT:
+                            if (inputFrame.getVisibility() == View.VISIBLE) {
+                                inputFrame.getLocationInWindow(mTempPos);
+                                mTempRect.set(mTempPos[0], originalInsets.contentTopInsets,
+                                        mTempPos[0] + inputFrame.getWidth(),
+                                        mTempPos[1] + inputFrame.getHeight());
+                                touchableRegion = new Region(mTempRect);
+                            }
+                            break;
+                        case ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_VISIBLE:
+                            if (inputFrame.getVisibility() == View.VISIBLE) {
+                                inputFrame.getLocationInWindow(mTempPos);
+                                mTempRect.set(mTempPos[0], originalInsets.visibleTopInsets,
+                                        mTempPos[0] + inputFrame.getWidth(),
+                                        mTempPos[1] + inputFrame.getHeight());
+                                touchableRegion = new Region(mTempRect);
+                            }
+                            break;
+                        case ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_REGION:
+                            touchableRegion = new Region();
+                            touchableRegion.set(originalInsets.touchableRegion);
+                            break;
+                    }
+                    // Hereafter "mTempRect" means a navigation bar rect.
+                    mTempRect.set(decor.getLeft(), decor.getBottom() - systemInsets.bottom,
+                            decor.getRight(), decor.getBottom());
+                    if (touchableRegion == null) {
+                        touchableRegion = new Region(mTempRect);
+                    } else {
+                        touchableRegion.union(mTempRect);
+                    }
+
+                    dest.touchableRegion.set(touchableRegion);
+                    dest.setTouchableInsets(
+                            ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_REGION);
+                }
 
                 // TODO(b/215443343): See if we can use View#OnLayoutChangeListener().
                 // TODO(b/215443343): See if we can replace DecorView#mNavigationColorViewState.view
diff --git a/core/java/android/preference/SeekBarVolumizer.java b/core/java/android/preference/SeekBarVolumizer.java
index 6f2a915..3f40139 100644
--- a/core/java/android/preference/SeekBarVolumizer.java
+++ b/core/java/android/preference/SeekBarVolumizer.java
@@ -37,7 +37,6 @@
 import android.os.HandlerThread;
 import android.os.Message;
 import android.preference.VolumePreference.VolumeStore;
-import android.provider.DeviceConfig;
 import android.provider.Settings;
 import android.provider.Settings.Global;
 import android.provider.Settings.System;
@@ -47,7 +46,6 @@
 import android.widget.SeekBar.OnSeekBarChangeListener;
 
 import com.android.internal.annotations.GuardedBy;
-import com.android.internal.config.sysui.SystemUiDeviceConfigFlags;
 import com.android.internal.os.SomeArgs;
 
 import java.util.concurrent.TimeUnit;
@@ -295,14 +293,8 @@
         if (zenMuted) {
             mSeekBar.setProgress(mLastAudibleStreamVolume, true);
         } else if (mNotificationOrRing && mRingerMode == AudioManager.RINGER_MODE_VIBRATE) {
-            /**
-             * the first variable above is preserved and the conditions below are made explicit
-             * so that when user attempts to slide the notification seekbar out of vibrate the
-             * seekbar doesn't wrongly snap back to 0 when the streams aren't aliased
-             */
-            if (!DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_SYSTEMUI,
-                    SystemUiDeviceConfigFlags.VOLUME_SEPARATE_NOTIFICATION, false)
-                    || mStreamType == AudioManager.STREAM_RING
+            // For ringer-mode affected streams, show volume as zero when ringermode is vibrate
+            if (mStreamType == AudioManager.STREAM_RING
                     || (mStreamType == AudioManager.STREAM_NOTIFICATION && mMuted)) {
                 mSeekBar.setProgress(0, true);
             }
@@ -397,9 +389,7 @@
         // set the time of stop volume
         if ((mStreamType == AudioManager.STREAM_VOICE_CALL
                 || mStreamType == AudioManager.STREAM_RING
-                || (DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_SYSTEMUI,
-                SystemUiDeviceConfigFlags.VOLUME_SEPARATE_NOTIFICATION, false)
-                && mStreamType == AudioManager.STREAM_NOTIFICATION)
+                || mStreamType == AudioManager.STREAM_NOTIFICATION
                 || mStreamType == AudioManager.STREAM_ALARM)) {
             sStopVolumeTime = java.lang.System.currentTimeMillis();
         }
@@ -686,10 +676,7 @@
         }
 
         private void updateVolumeSlider(int streamType, int streamValue) {
-            final boolean streamMatch =  !DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_SYSTEMUI,
-                    SystemUiDeviceConfigFlags.VOLUME_SEPARATE_NOTIFICATION, false)
-                    && mNotificationOrRing ? isNotificationOrRing(streamType) :
-                    streamType == mStreamType;
+            final boolean streamMatch = (streamType == mStreamType);
             if (mSeekBar != null && streamMatch && streamValue != -1) {
                 final boolean muted = mAudioManager.isStreamMute(mStreamType)
                         || streamValue == 0;
diff --git a/core/java/android/view/InsetsFrameProvider.java b/core/java/android/view/InsetsFrameProvider.java
index 470c280..a47f34f 100644
--- a/core/java/android/view/InsetsFrameProvider.java
+++ b/core/java/android/view/InsetsFrameProvider.java
@@ -164,6 +164,10 @@
         return mFlags;
     }
 
+    public boolean hasFlags(@Flags int mask) {
+        return (mFlags & mask) == mask;
+    }
+
     public InsetsFrameProvider setInsetsSize(Insets insetsSize) {
         mInsetsSize = insetsSize;
         return this;
diff --git a/core/java/android/view/InsetsSource.java b/core/java/android/view/InsetsSource.java
index 9fc42ff..e101849 100644
--- a/core/java/android/view/InsetsSource.java
+++ b/core/java/android/view/InsetsSource.java
@@ -58,9 +58,20 @@
      */
     public static final int FLAG_SUPPRESS_SCRIM = 1;
 
+    /**
+     * Controls whether the insets frame will be used to move {@link RoundedCorner} inward with the
+     * insets frame size when calculating the rounded corner insets to other windows.
+     *
+     * For example, task bar will draw fake rounded corners above itself, so we need to move the
+     * rounded corner up by the task bar insets size to make other windows see a rounded corner
+     * above the task bar.
+     */
+    public static final int FLAG_INSETS_ROUNDED_CORNER = 1 << 1;
+
     @Retention(RetentionPolicy.SOURCE)
     @IntDef(flag = true, prefix = "FLAG_", value = {
             FLAG_SUPPRESS_SCRIM,
+            FLAG_INSETS_ROUNDED_CORNER,
     })
     public @interface Flags {}
 
@@ -78,7 +89,6 @@
     private @Nullable Rect mVisibleFrame;
 
     private boolean mVisible;
-    private boolean mInsetsRoundedCornerFrame;
 
     private final Rect mTmpFrame = new Rect();
 
@@ -98,7 +108,6 @@
                 ? new Rect(other.mVisibleFrame)
                 : null;
         mFlags = other.mFlags;
-        mInsetsRoundedCornerFrame = other.mInsetsRoundedCornerFrame;
     }
 
     public void set(InsetsSource other) {
@@ -108,7 +117,6 @@
                 ? new Rect(other.mVisibleFrame)
                 : null;
         mFlags = other.mFlags;
-        mInsetsRoundedCornerFrame = other.mInsetsRoundedCornerFrame;
     }
 
     public InsetsSource setFrame(int left, int top, int right, int bottom) {
@@ -136,6 +144,11 @@
         return this;
     }
 
+    public InsetsSource setFlags(@Flags int flags, @Flags int mask) {
+        mFlags = (mFlags & ~mask) | (flags & mask);
+        return this;
+    }
+
     public int getId() {
         return mId;
     }
@@ -160,20 +173,15 @@
         return mFlags;
     }
 
+    public boolean hasFlags(int flags) {
+        return (mFlags & flags) == flags;
+    }
+
     boolean isUserControllable() {
         // If mVisibleFrame is null, it will be the same area as mFrame.
         return mVisibleFrame == null || !mVisibleFrame.isEmpty();
     }
 
-    public boolean insetsRoundedCornerFrame() {
-        return mInsetsRoundedCornerFrame;
-    }
-
-    public InsetsSource setInsetsRoundedCornerFrame(boolean insetsRoundedCornerFrame) {
-        mInsetsRoundedCornerFrame = insetsRoundedCornerFrame;
-        return this;
-    }
-
     /**
      * Calculates the insets this source will cause to a client window.
      *
@@ -317,6 +325,9 @@
         if ((flags & FLAG_SUPPRESS_SCRIM) != 0) {
             joiner.add("SUPPRESS_SCRIM");
         }
+        if ((flags & FLAG_INSETS_ROUNDED_CORNER) != 0) {
+            joiner.add("INSETS_ROUNDED_CORNER");
+        }
         return joiner.toString();
     }
 
@@ -347,7 +358,6 @@
         }
         pw.print(" visible="); pw.print(mVisible);
         pw.print(" flags="); pw.print(flagsToString(mFlags));
-        pw.print(" insetsRoundedCornerFrame="); pw.print(mInsetsRoundedCornerFrame);
         pw.println();
     }
 
@@ -372,14 +382,12 @@
         if (mFlags != that.mFlags) return false;
         if (excludeInvisibleImeFrames && !mVisible && mType == WindowInsets.Type.ime()) return true;
         if (!Objects.equals(mVisibleFrame, that.mVisibleFrame)) return false;
-        if (mInsetsRoundedCornerFrame != that.mInsetsRoundedCornerFrame) return false;
         return mFrame.equals(that.mFrame);
     }
 
     @Override
     public int hashCode() {
-        return Objects.hash(mId, mType, mFrame, mVisibleFrame, mVisible, mFlags,
-                mInsetsRoundedCornerFrame);
+        return Objects.hash(mId, mType, mFrame, mVisibleFrame, mVisible, mFlags);
     }
 
     public InsetsSource(Parcel in) {
@@ -393,7 +401,6 @@
         }
         mVisible = in.readBoolean();
         mFlags = in.readInt();
-        mInsetsRoundedCornerFrame = in.readBoolean();
     }
 
     @Override
@@ -414,7 +421,6 @@
         }
         dest.writeBoolean(mVisible);
         dest.writeInt(mFlags);
-        dest.writeBoolean(mInsetsRoundedCornerFrame);
     }
 
     @Override
@@ -424,7 +430,6 @@
                 + " mFrame=" + mFrame.toShortString()
                 + " mVisible=" + mVisible
                 + " mFlags=[" + flagsToString(mFlags) + "]"
-                + (mInsetsRoundedCornerFrame ? " insetsRoundedCornerFrame" : "")
                 + "}";
     }
 
diff --git a/core/java/android/view/InsetsState.java b/core/java/android/view/InsetsState.java
index 5b974cd..61a7277 100644
--- a/core/java/android/view/InsetsState.java
+++ b/core/java/android/view/InsetsState.java
@@ -16,6 +16,7 @@
 
 package android.view;
 
+import static android.view.InsetsSource.FLAG_INSETS_ROUNDED_CORNER;
 import static android.view.InsetsStateProto.DISPLAY_CUTOUT;
 import static android.view.InsetsStateProto.DISPLAY_FRAME;
 import static android.view.InsetsStateProto.SOURCES;
@@ -219,7 +220,7 @@
         final Rect roundedCornerFrame = new Rect(mRoundedCornerFrame);
         for (int i = mSources.size() - 1; i >= 0; i--) {
             final InsetsSource source = mSources.valueAt(i);
-            if (source.insetsRoundedCornerFrame()) {
+            if (source.hasFlags(FLAG_INSETS_ROUNDED_CORNER)) {
                 final Insets insets = source.calculateInsets(roundedCornerFrame, false);
                 roundedCornerFrame.inset(insets);
             }
diff --git a/core/java/android/view/SurfaceControlRegistry.java b/core/java/android/view/SurfaceControlRegistry.java
index 095189a..67ac811 100644
--- a/core/java/android/view/SurfaceControlRegistry.java
+++ b/core/java/android/view/SurfaceControlRegistry.java
@@ -62,7 +62,6 @@
     private static class DefaultReporter implements Reporter {
         public void onMaxLayersExceeded(WeakHashMap<SurfaceControl, Long> surfaceControls,
                 int limit, PrintWriter pw) {
-            final int size = Math.min(surfaceControls.size(), limit);
             final long now = SystemClock.elapsedRealtime();
             final ArrayList<Map.Entry<SurfaceControl, Long>> entries = new ArrayList<>();
             for (Map.Entry<SurfaceControl, Long> entry : surfaceControls.entrySet()) {
@@ -71,6 +70,7 @@
             // Sort entries by time registered when dumping
             // TODO: Or should it sort by name?
             entries.sort((o1, o2) -> (int) (o1.getValue() - o2.getValue()));
+            final int size = Math.min(entries.size(), limit);
 
             pw.println("SurfaceControlRegistry");
             pw.println("----------------------");
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 441636d..f1cde3b 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -7734,13 +7734,14 @@
         sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_LONG_CLICKED);
 
         boolean handled = false;
-        final ListenerInfo li = mListenerInfo;
+        final OnLongClickListener listener =
+                mListenerInfo == null ? null : mListenerInfo.mOnLongClickListener;
         boolean shouldPerformHapticFeedback = true;
-        if (li != null && li.mOnLongClickListener != null) {
-            handled = li.mOnLongClickListener.onLongClick(View.this);
+        if (listener != null) {
+            handled = listener.onLongClick(View.this);
             if (handled) {
-                shouldPerformHapticFeedback =
-                        li.mOnLongClickListener.onLongClickUseDefaultHapticFeedback(View.this);
+                shouldPerformHapticFeedback = listener.onLongClickUseDefaultHapticFeedback(
+                        View.this);
             }
         }
         if (!handled) {
diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java
index e95ba79..0e72ea8 100644
--- a/core/java/android/view/WindowManager.java
+++ b/core/java/android/view/WindowManager.java
@@ -4244,17 +4244,6 @@
         public InsetsFrameProvider[] providedInsets;
 
         /**
-         * If specified, the frame that used to calculate relative {@link RoundedCorner} will be
-         * the window frame of this window minus the insets that this window provides.
-         *
-         * Task bar will draw fake rounded corners above itself, so we need this insets to calculate
-         * correct rounded corners for this window.
-         *
-         * @hide
-         */
-        public boolean insetsRoundedCornerFrame = false;
-
-        /**
          * {@link LayoutParams} to be applied to the window when layout with a assigned rotation.
          * This will make layout during rotation change smoothly.
          *
@@ -4710,7 +4699,6 @@
             out.writeBoolean(mFitInsetsIgnoringVisibility);
             out.writeBoolean(preferMinimalPostProcessing);
             out.writeInt(mBlurBehindRadius);
-            out.writeBoolean(insetsRoundedCornerFrame);
             out.writeBoolean(mWallpaperTouchEventsEnabled);
             out.writeTypedArray(providedInsets, 0 /* parcelableFlags */);
             checkNonRecursiveParams();
@@ -4782,7 +4770,6 @@
             mFitInsetsIgnoringVisibility = in.readBoolean();
             preferMinimalPostProcessing = in.readBoolean();
             mBlurBehindRadius = in.readInt();
-            insetsRoundedCornerFrame = in.readBoolean();
             mWallpaperTouchEventsEnabled = in.readBoolean();
             providedInsets = in.createTypedArray(InsetsFrameProvider.CREATOR);
             paramsForRotation = in.createTypedArray(LayoutParams.CREATOR);
@@ -5090,11 +5077,6 @@
                 changes |= LAYOUT_CHANGED;
             }
 
-            if (insetsRoundedCornerFrame != o.insetsRoundedCornerFrame) {
-                insetsRoundedCornerFrame = o.insetsRoundedCornerFrame;
-                changes |= LAYOUT_CHANGED;
-            }
-
             if (paramsForRotation != o.paramsForRotation) {
                 if ((changes & LAYOUT_CHANGED) == 0) {
                     if (paramsForRotation != null && o.paramsForRotation != null
@@ -5332,10 +5314,6 @@
                     sb.append(prefix).append("    ").append(providedInsets[i]);
                 }
             }
-            if (insetsRoundedCornerFrame) {
-                sb.append(" insetsRoundedCornerFrame=");
-                sb.append(insetsRoundedCornerFrame);
-            }
             if (paramsForRotation != null && paramsForRotation.length != 0) {
                 sb.append(System.lineSeparator());
                 sb.append(prefix).append("  paramsForRotation:");
diff --git a/core/java/android/widget/RemoteViews.java b/core/java/android/widget/RemoteViews.java
index 34e6e49..5525336 100644
--- a/core/java/android/widget/RemoteViews.java
+++ b/core/java/android/widget/RemoteViews.java
@@ -2598,6 +2598,11 @@
         public int getActionTag() {
             return VIEW_GROUP_ACTION_ADD_TAG;
         }
+
+        @Override
+        public final void visitUris(@NonNull Consumer<Uri> visitor) {
+            mNestedViews.visitUris(visitor);
+        }
     }
 
     /**
diff --git a/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java b/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java
index 7ad2a68..8135f9c 100644
--- a/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java
+++ b/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java
@@ -549,11 +549,6 @@
             "task_manager_inform_job_scheduler_of_pending_app_stop";
 
     /**
-     * (boolean) Whether to show notification volume control slider separate from ring.
-     */
-    public static final String VOLUME_SEPARATE_NOTIFICATION = "volume_separate_notification";
-
-    /**
      * (boolean) Whether widget provider info would be saved to / loaded from system persistence
      * layer as opposed to individual manifests in respective apps.
      */
diff --git a/core/java/com/android/internal/util/LatencyTracker.java b/core/java/com/android/internal/util/LatencyTracker.java
index f277635..116c301c 100644
--- a/core/java/com/android/internal/util/LatencyTracker.java
+++ b/core/java/com/android/internal/util/LatencyTracker.java
@@ -367,28 +367,42 @@
      * using a single static object.
      */
     @VisibleForTesting
+    @RequiresPermission(Manifest.permission.READ_DEVICE_CONFIG)
     public void startListeningForLatencyTrackerConfigChanges() {
         final Context context = ActivityThread.currentApplication();
-        if (context != null
-                && context.checkCallingOrSelfPermission(READ_DEVICE_CONFIG) == PERMISSION_GRANTED) {
-            // Post initialization to the background in case we're running on the main thread.
-            BackgroundThread.getHandler().post(() -> this.updateProperties(
-                    DeviceConfig.getProperties(NAMESPACE_LATENCY_TRACKER)));
-            DeviceConfig.addOnPropertiesChangedListener(NAMESPACE_LATENCY_TRACKER,
-                    BackgroundThread.getExecutor(), mOnPropertiesChangedListener);
-        } else {
+        if (context == null) {
             if (DEBUG) {
-                if (context == null) {
-                    Log.d(TAG, "No application for " + ActivityThread.currentActivityThread());
-                } else {
-                    synchronized (mLock) {
-                        Log.d(TAG, "Initialized the LatencyTracker."
-                                + " (No READ_DEVICE_CONFIG permission to change configs)"
-                                + " enabled=" + mEnabled + ", package=" + context.getPackageName());
-                    }
+                Log.d(TAG, "No application for package: " + ActivityThread.currentPackageName());
+            }
+            return;
+        }
+        if (context.checkCallingOrSelfPermission(READ_DEVICE_CONFIG) != PERMISSION_GRANTED) {
+            if (DEBUG) {
+                synchronized (mLock) {
+                    Log.d(TAG, "Initialized the LatencyTracker."
+                            + " (No READ_DEVICE_CONFIG permission to change configs)"
+                            + " enabled=" + mEnabled + ", package=" + context.getPackageName());
                 }
             }
+            return;
         }
+
+        // Post initialization to the background in case we're running on the main thread.
+        BackgroundThread.getHandler().post(() -> {
+            try {
+                this.updateProperties(
+                        DeviceConfig.getProperties(NAMESPACE_LATENCY_TRACKER));
+                DeviceConfig.addOnPropertiesChangedListener(NAMESPACE_LATENCY_TRACKER,
+                        BackgroundThread.getExecutor(), mOnPropertiesChangedListener);
+            } catch (SecurityException ex) {
+                // In case of running tests that the main thread passes the check,
+                // but the background thread doesn't have necessary permissions.
+                // Swallow it since it's ok to ignore device config changes in the tests.
+                Log.d(TAG, "Can't get properties: READ_DEVICE_CONFIG granted="
+                        + context.checkCallingOrSelfPermission(READ_DEVICE_CONFIG)
+                        + ", package=" + context.getPackageName());
+            }
+        });
     }
 
     /**
diff --git a/core/java/com/android/internal/widget/PointerLocationView.java b/core/java/com/android/internal/widget/PointerLocationView.java
index f55d15d..fb4b026 100644
--- a/core/java/com/android/internal/widget/PointerLocationView.java
+++ b/core/java/com/android/internal/widget/PointerLocationView.java
@@ -163,6 +163,8 @@
     @UnsupportedAppUsage
     private boolean mPrintCoords = true;
 
+    private float mDensity;
+
     public PointerLocationView(Context c) {
         super(c);
         setFocusableInTouchMode(true);
@@ -357,19 +359,20 @@
 
                 // Draw current touch ellipse.
                 mPaint.setARGB(255, pressureLevel, 255 - pressureLevel, 128);
-                drawOval(canvas, ps.mCoords.x, ps.mCoords.y, ps.mCoords.touchMajor,
-                        ps.mCoords.touchMinor, ps.mCoords.orientation, mPaint);
+                drawOval(canvas, ps.mCoords.x, ps.mCoords.y, ps.mCoords.touchMajor * mDensity,
+                        ps.mCoords.touchMinor * mDensity, ps.mCoords.orientation, mPaint);
 
                 // Draw current tool ellipse.
                 mPaint.setARGB(255, pressureLevel, 128, 255 - pressureLevel);
-                drawOval(canvas, ps.mCoords.x, ps.mCoords.y, ps.mCoords.toolMajor,
-                        ps.mCoords.toolMinor, ps.mCoords.orientation, mPaint);
+                drawOval(canvas, ps.mCoords.x, ps.mCoords.y, ps.mCoords.toolMajor * mDensity,
+                        ps.mCoords.toolMinor * mDensity, ps.mCoords.orientation, mPaint);
 
                 // Draw the orientation arrow.
                 float arrowSize = ps.mCoords.toolMajor * 0.7f;
                 if (arrowSize < 20) {
                     arrowSize = 20;
                 }
+                arrowSize *= mDensity;
                 mPaint.setARGB(255, pressureLevel, 255, 0);
                 float orientationVectorX = (float) (Math.sin(ps.mCoords.orientation)
                         * arrowSize);
@@ -398,7 +401,7 @@
                 canvas.drawCircle(
                         ps.mCoords.x + orientationVectorX * tiltScale,
                         ps.mCoords.y + orientationVectorY * tiltScale,
-                        3.0f, mPaint);
+                        3.0f * mDensity, mPaint);
 
                 // Draw the current bounding box
                 if (ps.mHasBoundingBox) {
@@ -1003,10 +1006,10 @@
 
     // Compute size by display density.
     private void configureDensityDependentFactors() {
-        final float density = getResources().getDisplayMetrics().density;
-        mTextPaint.setTextSize(10 * density);
-        mPaint.setStrokeWidth(1 * density);
-        mCurrentPointPaint.setStrokeWidth(1 * density);
-        mPathPaint.setStrokeWidth(1 * density);
+        mDensity = getResources().getDisplayMetrics().density;
+        mTextPaint.setTextSize(10 * mDensity);
+        mPaint.setStrokeWidth(1 * mDensity);
+        mCurrentPointPaint.setStrokeWidth(1 * mDensity);
+        mPathPaint.setStrokeWidth(1 * mDensity);
     }
 }
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 114cea1..8473f9b 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -4429,6 +4429,14 @@
     -->
     <string name="config_defaultContentCaptureService" translatable="false"></string>
 
+    <!-- The package name for the system's content protection service.
+         This service must be trusted, as it can be activated without explicit consent of the user.
+         If no service with the specified name exists on the device, content protection will be
+         disabled.
+         Example: "com.android.contentprotection/.ContentProtectionService"
+    -->
+    <string name="config_defaultContentProtectionService" translatable="false"></string>
+
     <!-- The package name for the system's augmented autofill service.
          This service must be trusted, as it can be activated without explicit consent of the user.
          If no service with the specified name exists on the device, augmented autofill wil be
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 79bb97d..b3fd51a 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -3761,6 +3761,7 @@
   <java-symbol type="string" name="config_defaultTextClassifierPackage" />
   <java-symbol type="string" name="config_defaultWellbeingPackage" />
   <java-symbol type="string" name="config_defaultContentCaptureService" />
+  <java-symbol type="string" name="config_defaultContentProtectionService" />
   <java-symbol type="string" name="config_defaultAugmentedAutofillService" />
   <java-symbol type="string" name="config_defaultTranslationService" />
   <java-symbol type="string" name="config_defaultAppPredictionService" />
@@ -5132,4 +5133,5 @@
   <java-symbol type="style" name="ThemeOverlay.DeviceDefault.Dark.ActionBar.Accent" />
 
   <java-symbol type="drawable" name="focus_event_pressed_key_background" />
+  <java-symbol type="string" name="lockscreen_too_many_failed_attempts_countdown" />
 </resources>
diff --git a/core/tests/coretests/src/android/app/NotificationTest.java b/core/tests/coretests/src/android/app/NotificationTest.java
index c5b00c9..eba7f58 100644
--- a/core/tests/coretests/src/android/app/NotificationTest.java
+++ b/core/tests/coretests/src/android/app/NotificationTest.java
@@ -33,6 +33,8 @@
 import static android.app.Notification.EXTRA_PEOPLE_LIST;
 import static android.app.Notification.EXTRA_PICTURE;
 import static android.app.Notification.EXTRA_PICTURE_ICON;
+import static android.app.Notification.EXTRA_SUMMARY_TEXT;
+import static android.app.Notification.EXTRA_TITLE;
 import static android.app.Notification.MessagingStyle.Message.KEY_DATA_URI;
 import static android.app.Notification.MessagingStyle.Message.KEY_SENDER_PERSON;
 import static android.app.Notification.MessagingStyle.Message.KEY_TEXT;
@@ -76,6 +78,7 @@
 import android.os.Bundle;
 import android.os.Parcel;
 import android.os.Parcelable;
+import android.os.SystemProperties;
 import android.text.Spannable;
 import android.text.SpannableString;
 import android.text.SpannableStringBuilder;
@@ -111,6 +114,9 @@
     @Before
     public void setUp() {
         mContext = InstrumentationRegistry.getContext();
+        // TODO(b/169435530): remove this flag set once resolved.
+        SystemProperties.set("persist.sysui.notification.builder_extras_override",
+                Boolean.toString(false));
     }
 
     @Test
@@ -1481,6 +1487,107 @@
         Assert.assertEquals(actionWithFreeformRemoteInput, remoteInputActionPair.second);
     }
 
+    // Ensures that extras in a Notification Builder can be updated.
+    @Test
+    public void testExtras_cachedExtrasOverwrittenByUserProvided() {
+        // Sets the flag to new state.
+        // TODO(b/169435530): remove this set value once resolved.
+        SystemProperties.set("persist.sysui.notification.builder_extras_override",
+                Boolean.toString(true));
+        Bundle extras = new Bundle();
+        extras.putCharSequence(EXTRA_TITLE, "test title");
+        extras.putCharSequence(EXTRA_SUMMARY_TEXT, "summary text");
+
+        Notification.Builder builder = new Notification.Builder(mContext, "test id")
+                .addExtras(extras);
+
+        Notification notification = builder.build();
+        assertThat(notification.extras.getCharSequence(EXTRA_TITLE).toString()).isEqualTo(
+                "test title");
+        assertThat(notification.extras.getCharSequence(EXTRA_SUMMARY_TEXT).toString()).isEqualTo(
+                "summary text");
+
+        extras.putCharSequence(EXTRA_TITLE, "new title");
+        builder.addExtras(extras);
+        notification = builder.build();
+        assertThat(notification.extras.getCharSequence(EXTRA_TITLE).toString()).isEqualTo(
+                "new title");
+        assertThat(notification.extras.getCharSequence(EXTRA_SUMMARY_TEXT).toString()).isEqualTo(
+                "summary text");
+    }
+
+    // Ensures that extras in a Notification Builder can be updated by an extender.
+    @Test
+    public void testExtras_cachedExtrasOverwrittenByExtender() {
+        // Sets the flag to new state.
+        // TODO(b/169435530): remove this set value once resolved.
+        SystemProperties.set("persist.sysui.notification.builder_extras_override",
+                Boolean.toString(true));
+        Notification.CarExtender extender = new Notification.CarExtender().setColor(1234);
+
+        Notification notification = new Notification.Builder(mContext, "test id")
+                .extend(extender).build();
+
+        extender.setColor(5678);
+
+        Notification.Builder.recoverBuilder(mContext, notification).extend(extender).build();
+
+        Notification.CarExtender recoveredExtender = new Notification.CarExtender(notification);
+        assertThat(recoveredExtender.getColor()).isEqualTo(5678);
+    }
+
+    // Validates pre-flag flip behavior, that extras in a Notification Builder cannot be updated.
+    // TODO(b/169435530): remove this test once resolved.
+    @Test
+    public void testExtras_cachedExtrasOverwrittenByUserProvidedOld() {
+        // Sets the flag to old state.
+        SystemProperties.set("persist.sysui.notification.builder_extras_override",
+                Boolean.toString(false));
+
+        Bundle extras = new Bundle();
+        extras.putCharSequence(EXTRA_TITLE, "test title");
+        extras.putCharSequence(EXTRA_SUMMARY_TEXT, "summary text");
+
+        Notification.Builder builder = new Notification.Builder(mContext, "test id")
+                .addExtras(extras);
+
+        Notification notification = builder.build();
+        assertThat(notification.extras.getCharSequence(EXTRA_TITLE).toString()).isEqualTo(
+                "test title");
+        assertThat(notification.extras.getCharSequence(EXTRA_SUMMARY_TEXT).toString()).isEqualTo(
+                "summary text");
+
+        extras.putCharSequence(EXTRA_TITLE, "new title");
+        builder.addExtras(extras);
+        notification = builder.build();
+        assertThat(notification.extras.getCharSequence(EXTRA_TITLE).toString()).isEqualTo(
+                "test title");
+        assertThat(notification.extras.getCharSequence(EXTRA_SUMMARY_TEXT).toString()).isEqualTo(
+                "summary text");
+    }
+
+    // Validates pre-flag flip behavior, that extras in a Notification Builder cannot be updated
+    // by an extender.
+    // TODO(b/169435530): remove this test once resolved.
+    @Test
+    public void testExtras_cachedExtrasOverwrittenByExtenderOld() {
+        // Sets the flag to old state.
+        SystemProperties.set("persist.sysui.notification.builder_extras_override",
+                Boolean.toString(false));
+
+        Notification.CarExtender extender = new Notification.CarExtender().setColor(1234);
+
+        Notification notification = new Notification.Builder(mContext, "test id")
+                .extend(extender).build();
+
+        extender.setColor(5678);
+
+        Notification.Builder.recoverBuilder(mContext, notification).extend(extender).build();
+
+        Notification.CarExtender recoveredExtender = new Notification.CarExtender(notification);
+        assertThat(recoveredExtender.getColor()).isEqualTo(1234);
+    }
+
     private void assertValid(Notification.Colors c) {
         // Assert that all colors are populated
         assertThat(c.getBackgroundColor()).isNotEqualTo(Notification.COLOR_INVALID);
diff --git a/core/tests/coretests/src/android/widget/RemoteViewsTest.java b/core/tests/coretests/src/android/widget/RemoteViewsTest.java
index 4672226..7879801 100644
--- a/core/tests/coretests/src/android/widget/RemoteViewsTest.java
+++ b/core/tests/coretests/src/android/widget/RemoteViewsTest.java
@@ -716,6 +716,30 @@
     }
 
     @Test
+    public void visitUris_nestedViews() {
+        final RemoteViews outer = new RemoteViews(mPackage, R.layout.remote_views_test);
+
+        final RemoteViews inner = new RemoteViews(mPackage, 33);
+        final Uri imageUriI = Uri.parse("content://inner/image");
+        final Icon icon1 = Icon.createWithContentUri("content://inner/icon1");
+        final Icon icon2 = Icon.createWithContentUri("content://inner/icon2");
+        final Icon icon3 = Icon.createWithContentUri("content://inner/icon3");
+        final Icon icon4 = Icon.createWithContentUri("content://inner/icon4");
+        inner.setImageViewUri(R.id.image, imageUriI);
+        inner.setTextViewCompoundDrawables(R.id.text, icon1, icon2, icon3, icon4);
+
+        outer.addView(R.id.layout, inner);
+
+        Consumer<Uri> visitor = (Consumer<Uri>) spy(Consumer.class);
+        outer.visitUris(visitor);
+        verify(visitor, times(1)).accept(eq(imageUriI));
+        verify(visitor, times(1)).accept(eq(icon1.getUri()));
+        verify(visitor, times(1)).accept(eq(icon2.getUri()));
+        verify(visitor, times(1)).accept(eq(icon3.getUri()));
+        verify(visitor, times(1)).accept(eq(icon4.getUri()));
+    }
+
+    @Test
     public void visitUris_separateOrientation() {
         final RemoteViews landscape = new RemoteViews(mPackage, R.layout.remote_views_test);
         final Uri imageUriL = Uri.parse("content://landscape/image");
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
index 89f4890..3b45d5d 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
@@ -1418,7 +1418,7 @@
         if (splitRule instanceof SplitPairRule && ((SplitPairRule) splitRule).shouldClearTop()) {
             removeExistingSecondaryContainers(wct, primaryContainer);
         }
-        primaryContainer.getTaskContainer().mSplitContainers.add(splitContainer);
+        primaryContainer.getTaskContainer().addSplitContainer(splitContainer);
     }
 
     /** Cleanups all the dependencies when the TaskFragment is entering PIP. */
@@ -1430,8 +1430,9 @@
             return;
         }
         final List<SplitContainer> splitsToRemove = new ArrayList<>();
+        final List<SplitContainer> splitContainers = taskContainer.getSplitContainers();
         final Set<TaskFragmentContainer> containersToUpdate = new ArraySet<>();
-        for (SplitContainer splitContainer : taskContainer.mSplitContainers) {
+        for (SplitContainer splitContainer : splitContainers) {
             if (splitContainer.getPrimaryContainer() != container
                     && splitContainer.getSecondaryContainer() != container) {
                 continue;
@@ -1449,7 +1450,7 @@
             }
         }
         container.resetDependencies();
-        taskContainer.mSplitContainers.removeAll(splitsToRemove);
+        taskContainer.removeSplitContainers(splitsToRemove);
         // If there is any TaskFragment split with the PIP TaskFragment, update their presentations
         // since the split is dismissed.
         // We don't want to close any of them even if they are dependencies of the PIP TaskFragment.
@@ -1481,7 +1482,8 @@
 
         // Cleanup any split references.
         final List<SplitContainer> containersToRemove = new ArrayList<>();
-        for (SplitContainer splitContainer : taskContainer.mSplitContainers) {
+        final List<SplitContainer> splitContainers = taskContainer.getSplitContainers();
+        for (SplitContainer splitContainer : splitContainers) {
             if (containersToRemove.contains(splitContainer)) {
                 // Don't need to check because it has been in the remove list.
                 continue;
@@ -1492,7 +1494,7 @@
                 containersToRemove.add(splitContainer);
             }
         }
-        taskContainer.mSplitContainers.removeAll(containersToRemove);
+        taskContainer.removeSplitContainers(containersToRemove);
 
         // Cleanup any dependent references.
         for (TaskFragmentContainer containerToUpdate : taskContainer.mContainers) {
@@ -1629,7 +1631,7 @@
     /** Whether the given split is the topmost split in the Task. */
     private boolean isTopMostSplit(@NonNull SplitContainer splitContainer) {
         final List<SplitContainer> splitContainers = splitContainer.getPrimaryContainer()
-                .getTaskContainer().mSplitContainers;
+                .getTaskContainer().getSplitContainers();
         return splitContainer == splitContainers.get(splitContainers.size() - 1);
     }
 
@@ -1641,7 +1643,8 @@
         if (container == null) {
             return null;
         }
-        final List<SplitContainer> splitContainers = container.getTaskContainer().mSplitContainers;
+        final List<SplitContainer> splitContainers =
+                container.getTaskContainer().getSplitContainers();
         if (splitContainers.isEmpty()) {
             return null;
         }
@@ -1665,7 +1668,7 @@
             @NonNull TaskFragmentContainer firstContainer,
             @NonNull TaskFragmentContainer secondContainer) {
         final List<SplitContainer> splitContainers = firstContainer.getTaskContainer()
-                .mSplitContainers;
+                .getSplitContainers();
         for (int i = splitContainers.size() - 1; i >= 0; i--) {
             final SplitContainer splitContainer = splitContainers.get(i);
             final TaskFragmentContainer primary = splitContainer.getPrimaryContainer();
@@ -1945,7 +1948,7 @@
     @GuardedBy("mLock")
     SplitContainer getSplitContainer(@NonNull IBinder token) {
         for (int i = mTaskContainers.size() - 1; i >= 0; i--) {
-            final List<SplitContainer> containers = mTaskContainers.valueAt(i).mSplitContainers;
+            final List<SplitContainer> containers = mTaskContainers.valueAt(i).getSplitContainers();
             for (SplitContainer container : containers) {
                 if (container.getToken().equals(token)) {
                     return container;
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java
index 4b15bb1..0626510 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java
@@ -55,7 +55,7 @@
 
     /** Active split pairs in this Task. */
     @NonNull
-    final List<SplitContainer> mSplitContainers = new ArrayList<>();
+    private final List<SplitContainer> mSplitContainers = new ArrayList<>();
 
     @NonNull
     private final Configuration mConfiguration;
@@ -207,6 +207,19 @@
         return false;
     }
 
+    @NonNull
+    List<SplitContainer> getSplitContainers() {
+        return new ArrayList<>(mSplitContainers);
+    }
+
+    void addSplitContainer(@NonNull SplitContainer splitContainer) {
+        mSplitContainers.add(splitContainer);
+    }
+
+    void removeSplitContainers(@NonNull List<SplitContainer> containers) {
+        mSplitContainers.removeAll(containers);
+    }
+
     /** Adds the descriptors of split states in this Task to {@code outSplitStates}. */
     void getSplitStates(@NonNull List<SplitInfo> outSplitStates) {
         for (SplitContainer container : mSplitContainers) {
diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java
index ff08782..82692a5 100644
--- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java
+++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java
@@ -320,11 +320,11 @@
         doReturn(tf).when(splitContainer).getSecondaryContainer();
         doReturn(createTestTaskContainer()).when(splitContainer).getTaskContainer();
         doReturn(createSplitRule(mActivity, mActivity)).when(splitContainer).getSplitRule();
-        final List<SplitContainer> splitContainers =
-                mSplitController.getTaskContainer(TASK_ID).mSplitContainers;
-        splitContainers.add(splitContainer);
+        final TaskContainer taskContainer = mSplitController.getTaskContainer(TASK_ID);
+        taskContainer.addSplitContainer(splitContainer);
         // Add a mock SplitContainer on top of splitContainer
-        splitContainers.add(1, mock(SplitContainer.class));
+        final SplitContainer splitContainer2 = mock(SplitContainer.class);
+        taskContainer.addSplitContainer(splitContainer2);
 
         mSplitController.updateContainer(mTransaction, tf);
 
@@ -332,7 +332,9 @@
 
         // Verify if one or both containers in the top SplitContainer are finished,
         // dismissPlaceholder() won't be called.
-        splitContainers.remove(1);
+        final ArrayList<SplitContainer> splitContainersToRemove = new ArrayList<>();
+        splitContainersToRemove.add(splitContainer2);
+        taskContainer.removeSplitContainers(splitContainersToRemove);
         doReturn(true).when(tf).isFinished();
 
         mSplitController.updateContainer(mTransaction, tf);
@@ -377,7 +379,7 @@
         doReturn(true).when(taskContainer).isVisible();
         mSplitController.updateContainer(mTransaction, taskFragmentContainer);
 
-        verify(mSplitPresenter).updateSplitContainer(taskContainer.mSplitContainers.get(0),
+        verify(mSplitPresenter).updateSplitContainer(taskContainer.getSplitContainers().get(0),
                 mTransaction);
     }
 
@@ -1091,7 +1093,7 @@
         verify(mTransaction).finishActivity(secondaryActivity0.getActivityToken());
         verify(mTransaction).finishActivity(secondaryActivity1.getActivityToken());
         assertTrue(taskContainer.mContainers.isEmpty());
-        assertTrue(taskContainer.mSplitContainers.isEmpty());
+        assertTrue(taskContainer.getSplitContainers().isEmpty());
     }
 
     @Test
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingController.java
index fbdbd3e..7b37d59 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingController.java
@@ -16,6 +16,7 @@
 
 package com.android.wm.shell.activityembedding;
 
+import static android.app.ActivityOptions.ANIM_SCENE_TRANSITION;
 import static android.window.TransitionInfo.FLAG_FILLS_TASK;
 import static android.window.TransitionInfo.FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY;
 
@@ -111,6 +112,11 @@
         if (containsNonEmbeddedChange && !handleNonEmbeddedChanges(changes)) {
             return false;
         }
+        final TransitionInfo.AnimationOptions options = info.getAnimationOptions();
+        if (options != null && options.getType() == ANIM_SCENE_TRANSITION) {
+            // Scene-transition will be handled by app side.
+            return false;
+        }
 
         // Start ActivityEmbedding animation.
         mTransitionCallbacks.put(transition, finishCallback);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
index 3eb9fa2..988ad8c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
@@ -1846,7 +1846,7 @@
             if (mStackView != null) {
                 mStackView.setVisibility(VISIBLE);
             }
-            if (mLayerView != null && isStackExpanded()) {
+            if (mLayerView != null) {
                 mLayerView.setVisibility(VISIBLE);
             }
         }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/TabletopModeController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/TabletopModeController.java
index ac6e4c2..53683c6 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/TabletopModeController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/TabletopModeController.java
@@ -54,14 +54,6 @@
         DevicePostureController.OnDevicePostureChangedListener,
         DisplayController.OnDisplaysChangedListener {
     /**
-     * When {@code true}, floating windows like PiP would auto move to the position
-     * specified by {@link #PREFER_TOP_HALF_IN_TABLETOP} when in tabletop mode.
-     */
-    private static final boolean ENABLE_MOVE_FLOATING_WINDOW_IN_TABLETOP =
-            SystemProperties.getBoolean(
-                    "persist.wm.debug.enable_move_floating_window_in_tabletop", true);
-
-    /**
      * Prefer the {@link #PREFERRED_TABLETOP_HALF_TOP} if this flag is enabled,
      * {@link #PREFERRED_TABLETOP_HALF_BOTTOM} otherwise.
      * See also {@link #getPreferredHalfInTabletopMode()}.
@@ -162,14 +154,6 @@
         }
     }
 
-    /**
-     * @return {@code true} if floating windows like PiP would auto move to the position
-     * specified by {@link #getPreferredHalfInTabletopMode()} when in tabletop mode.
-     */
-    public boolean enableMoveFloatingWindowInTabletop() {
-        return ENABLE_MOVE_FLOATING_WINDOW_IN_TABLETOP;
-    }
-
     /** @return Preferred half for floating windows like PiP when in tabletop mode. */
     @PreferredTabletopHalf
     public int getPreferredHalfInTabletopMode() {
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 69f0bad..7f362f3 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
@@ -222,7 +222,7 @@
             for (int i = insetsState.sourceSize() - 1; i >= 0; i--) {
                 final InsetsSource source = insetsState.sourceAt(i);
                 if (source.getType() == WindowInsets.Type.navigationBars()
-                        && source.insetsRoundedCornerFrame()) {
+                        && source.hasFlags(InsetsSource.FLAG_INSETS_ROUNDED_CORNER)) {
                     mTempRect.inset(source.calculateVisibleInsets(mTempRect));
                 }
             }
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 753dfa7..a9ccdf6 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
@@ -293,6 +293,9 @@
         }
 
         if (mResizingIconView == null) {
+            if (mRunningAnimationCount == 0 && animFinishedCallback != null) {
+                animFinishedCallback.accept(false);
+            }
             return;
         }
 
@@ -311,6 +314,9 @@
                         releaseDecor(finishT);
                         finishT.apply();
                         finishT.close();
+                        if (mRunningAnimationCount == 0 && animFinishedCallback != null) {
+                            animFinishedCallback.accept(true);
+                        }
                     }
                 });
                 return;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipMediaController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipMediaController.java
index 65a12d6..2590cab 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipMediaController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipMediaController.java
@@ -252,13 +252,16 @@
     // It can be removed when min_sdk of the app is set to 31 or greater.
     @SuppressLint("NewApi")
     private List<RemoteAction> getMediaActions() {
-        if (mMediaController == null || mMediaController.getPlaybackState() == null) {
+        // Cache the PlaybackState since it's a Binder call.
+        final PlaybackState playbackState;
+        if (mMediaController == null
+                || (playbackState = mMediaController.getPlaybackState()) == null) {
             return Collections.emptyList();
         }
 
         ArrayList<RemoteAction> mediaActions = new ArrayList<>();
-        boolean isPlaying = mMediaController.getPlaybackState().isActive();
-        long actions = mMediaController.getPlaybackState().getActions();
+        boolean isPlaying = playbackState.isActive();
+        long actions = playbackState.getActions();
 
         // Prev action
         mPrevAction.setEnabled((actions & PlaybackState.ACTION_SKIP_TO_PREVIOUS) != 0);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java
index bfc1fb9..3c7ce3c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java
@@ -249,11 +249,6 @@
                     finishTransaction);
         }
 
-        // Fade in the fadeout PIP when the fixed rotation is finished.
-        if (mPipTransitionState.isInPip() && !mInFixedRotation && mHasFadeOut) {
-            fadeExistingPip(true /* show */);
-        }
-
         return false;
     }
 
@@ -1056,6 +1051,12 @@
                 .crop(finishTransaction, leash, destBounds)
                 .round(finishTransaction, leash, isInPip)
                 .shadow(finishTransaction, leash, isInPip);
+        // Make sure the PiP keeps invisible if it was faded out. If it needs to fade in, that will
+        // be handled by onFixedRotationFinished().
+        if (isInPip && mHasFadeOut) {
+            startTransaction.setAlpha(leash, 0f);
+            finishTransaction.setAlpha(leash, 0f);
+        }
     }
 
     /** Hides and shows the existing PIP during fixed rotation transition of other activities. */
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PhonePipKeepClearAlgorithm.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PhonePipKeepClearAlgorithm.java
index ed8dc7de..fc674a8 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PhonePipKeepClearAlgorithm.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PhonePipKeepClearAlgorithm.java
@@ -65,9 +65,18 @@
         }
         Rect pipBounds = new Rect(startingBounds);
 
-        // move PiP towards corner if user hasn't moved it manually or the flag is on
-        if (mKeepClearAreaGravityEnabled
-                || (!pipBoundsState.hasUserMovedPip() && !pipBoundsState.hasUserResizedPip())) {
+        boolean shouldApplyGravity = false;
+        // if PiP is outside of screen insets, reposition using gravity
+        if (!insets.contains(pipBounds)) {
+            shouldApplyGravity = true;
+        }
+        // if user has not interacted with PiP, reposition using gravity
+        if (!pipBoundsState.hasUserMovedPip() && !pipBoundsState.hasUserResizedPip()) {
+            shouldApplyGravity = true;
+        }
+
+        // apply gravity that will position PiP in bottom left or bottom right corner within insets
+        if (mKeepClearAreaGravityEnabled || shouldApplyGravity) {
             float snapFraction = pipBoundsAlgorithm.getSnapFraction(startingBounds);
             int verticalGravity = Gravity.BOTTOM;
             int horizontalGravity;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
index 63181da..6a861ce 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
@@ -677,7 +677,6 @@
                 });
 
         mTabletopModeController.registerOnTabletopModeChangedListener((isInTabletopMode) -> {
-            if (!mTabletopModeController.enableMoveFloatingWindowInTabletop()) return;
             final String tag = "tabletop-mode";
             if (!isInTabletopMode) {
                 mPipBoundsState.removeNamedUnrestrictedKeepClearArea(tag);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java
index 5c9709c..f35eda6 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java
@@ -371,7 +371,8 @@
      * Find the background task that match the given component.
      */
     @Nullable
-    public ActivityManager.RecentTaskInfo findTaskInBackground(ComponentName componentName) {
+    public ActivityManager.RecentTaskInfo findTaskInBackground(ComponentName componentName,
+            int userId) {
         if (componentName == null) {
             return null;
         }
@@ -383,7 +384,7 @@
             if (task.isVisible) {
                 continue;
             }
-            if (componentName.equals(task.baseIntent.getComponent())) {
+            if (componentName.equals(task.baseIntent.getComponent()) && userId == task.userId) {
                 return task;
             }
         }
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 34701f1..ea33a1f 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
@@ -723,7 +723,7 @@
                 // in the background with priority.
                 final ActivityManager.RecentTaskInfo taskInfo = mRecentTasksOptional
                         .map(recentTasks -> recentTasks.findTaskInBackground(
-                                intent.getIntent().getComponent()))
+                                intent.getIntent().getComponent(), userId1))
                         .orElse(null);
                 if (taskInfo != null) {
                     startTask(taskInfo.taskId, position, options);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java
index 14ea86a..d2b0e28 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java
@@ -87,6 +87,14 @@
         mStageCoordinator = stageCoordinator;
     }
 
+    private void initTransition(@NonNull IBinder transition,
+            @NonNull SurfaceControl.Transaction finishTransaction,
+            @NonNull Transitions.TransitionFinishCallback finishCallback) {
+        mAnimatingTransition = transition;
+        mFinishTransaction = finishTransaction;
+        mFinishCallback = finishCallback;
+    }
+
     /** Play animation for enter transition or dismiss transition. */
     void playAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
             @NonNull SurfaceControl.Transaction startTransaction,
@@ -94,9 +102,7 @@
             @NonNull Transitions.TransitionFinishCallback finishCallback,
             @NonNull WindowContainerToken mainRoot, @NonNull WindowContainerToken sideRoot,
             @NonNull WindowContainerToken topRoot) {
-        mFinishCallback = finishCallback;
-        mAnimatingTransition = transition;
-        mFinishTransaction = finishTransaction;
+        initTransition(transition, finishTransaction, finishCallback);
 
         final TransitSession pendingTransition = getPendingTransition(transition);
         if (pendingTransition != null) {
@@ -220,6 +226,45 @@
         onFinish(null /* wct */, null /* wctCB */);
     }
 
+    /** Play animation for drag divider dismiss transition. */
+    void playDragDismissAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
+            @NonNull SurfaceControl.Transaction startTransaction,
+            @NonNull SurfaceControl.Transaction finishTransaction,
+            @NonNull Transitions.TransitionFinishCallback finishCallback,
+            @NonNull WindowContainerToken toTopRoot, @NonNull SplitDecorManager toTopDecor,
+            @NonNull WindowContainerToken topRoot) {
+        initTransition(transition, finishTransaction, finishCallback);
+
+        for (int i = info.getChanges().size() - 1; i >= 0; --i) {
+            final TransitionInfo.Change change = info.getChanges().get(i);
+            final SurfaceControl leash = change.getLeash();
+
+            if (toTopRoot.equals(change.getContainer())) {
+                startTransaction.setAlpha(leash, 1.f);
+                startTransaction.show(leash);
+
+                ValueAnimator va = new ValueAnimator();
+                mAnimations.add(va);
+
+                toTopDecor.onResized(startTransaction, animated -> {
+                    mAnimations.remove(va);
+                    if (animated) {
+                        mTransitions.getMainExecutor().execute(() -> {
+                            onFinish(null /* wct */, null /* wctCB */);
+                        });
+                    }
+                });
+            } else if (topRoot.equals(change.getContainer())) {
+                // Ensure it on top of all changes in transition.
+                startTransaction.setLayer(leash, Integer.MAX_VALUE);
+                startTransaction.setAlpha(leash, 1.f);
+                startTransaction.show(leash);
+            }
+        }
+        startTransaction.apply();
+        onFinish(null /* wct */, null /* wctCB */);
+    }
+
     /** Play animation for resize transition. */
     void playResizeAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
             @NonNull SurfaceControl.Transaction startTransaction,
@@ -227,9 +272,7 @@
             @NonNull Transitions.TransitionFinishCallback finishCallback,
             @NonNull WindowContainerToken mainRoot, @NonNull WindowContainerToken sideRoot,
             @NonNull SplitDecorManager mainDecor, @NonNull SplitDecorManager sideDecor) {
-        mFinishCallback = finishCallback;
-        mAnimatingTransition = transition;
-        mFinishTransaction = finishTransaction;
+        initTransition(transition, finishTransaction, finishCallback);
 
         for (int i = info.getChanges().size() - 1; i >= 0; --i) {
             final TransitionInfo.Change change = info.getChanges().get(i);
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 bf20567..e0fffff 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
@@ -1328,8 +1328,6 @@
             mIsExiting = true;
             childrenToTop.resetBounds(wct);
             wct.reorder(childrenToTop.mRootTaskInfo.token, true);
-            wct.setSmallestScreenWidthDp(childrenToTop.mRootTaskInfo.token,
-                    SMALLEST_SCREEN_WIDTH_DP_UNDEFINED);
         }
         wct.setReparentLeafTaskIfRelaunch(mRootTaskInfo.token,
                 false /* reparentLeafTaskIfRelaunch */);
@@ -1517,6 +1515,10 @@
 
     void finishEnterSplitScreen(SurfaceControl.Transaction t) {
         mSplitLayout.update(t);
+        mMainStage.getSplitDecorManager().inflate(mContext, mMainStage.mRootLeash,
+                getMainStageBounds());
+        mSideStage.getSplitDecorManager().inflate(mContext, mSideStage.mRootLeash,
+                getSideStageBounds());
         setDividerVisibility(true, t);
         // Ensure divider surface are re-parented back into the hierarchy at the end of the
         // transition. See Transition#buildFinishTransaction for more detail.
@@ -1989,13 +1991,15 @@
         final boolean mainStageToTop =
                 bottomOrRight ? mSideStagePosition == SPLIT_POSITION_BOTTOM_OR_RIGHT
                         : mSideStagePosition == SPLIT_POSITION_TOP_OR_LEFT;
+        final StageTaskListener toTopStage = mainStageToTop ? mMainStage : mSideStage;
         if (!ENABLE_SHELL_TRANSITIONS) {
-            exitSplitScreen(mainStageToTop ? mMainStage : mSideStage, reason);
+            exitSplitScreen(toTopStage, reason);
             return;
         }
 
         final int dismissTop = mainStageToTop ? STAGE_TYPE_MAIN : STAGE_TYPE_SIDE;
         final WindowContainerTransaction wct = new WindowContainerTransaction();
+        toTopStage.resetBounds(wct);
         prepareExitSplitScreen(dismissTop, wct);
         if (mRootTaskInfo != null) {
             wct.setDoNotPip(mRootTaskInfo.token);
@@ -2531,8 +2535,17 @@
             shouldAnimate = startPendingEnterAnimation(
                     mSplitTransitions.mPendingEnter, info, startTransaction, finishTransaction);
         } else if (mSplitTransitions.isPendingDismiss(transition)) {
+            final SplitScreenTransitions.DismissSession dismiss = mSplitTransitions.mPendingDismiss;
             shouldAnimate = startPendingDismissAnimation(
-                    mSplitTransitions.mPendingDismiss, info, startTransaction, finishTransaction);
+                    dismiss, info, startTransaction, finishTransaction);
+            if (shouldAnimate && dismiss.mReason == EXIT_REASON_DRAG_DIVIDER) {
+                final StageTaskListener toTopStage =
+                        dismiss.mDismissTop == STAGE_TYPE_MAIN ? mMainStage : mSideStage;
+                mSplitTransitions.playDragDismissAnimation(transition, info, startTransaction,
+                        finishTransaction, finishCallback, toTopStage.mRootTaskInfo.token,
+                        toTopStage.getSplitDecorManager(), mRootTaskInfo.token);
+                return true;
+            }
         } else if (mSplitTransitions.isPendingResize(transition)) {
             mSplitTransitions.playResizeAnimation(transition, info, startTransaction,
                     finishTransaction, finishCallback, mMainStage.mRootTaskInfo.token,
@@ -2787,6 +2800,10 @@
             mSplitTransitions.mPendingDismiss = null;
             return false;
         }
+        dismissTransition.setFinishedCallback((callbackWct, callbackT) -> {
+            mMainStage.getSplitDecorManager().release(callbackT);
+            mSideStage.getSplitDecorManager().release(callbackT);
+        });
 
         addDividerBarToTransition(info, false /* show */);
         return true;
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 e2e9270..da7d186 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
@@ -19,6 +19,7 @@
 import static android.app.ActivityTaskManager.INVALID_TASK_ID;
 import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
 import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
+import static android.content.res.Configuration.SMALLEST_SCREEN_WIDTH_DP_UNDEFINED;
 import static android.view.RemoteAnimationTarget.MODE_OPENING;
 
 import static com.android.wm.shell.common.split.SplitScreenConstants.CONTROLLED_ACTIVITY_TYPES;
@@ -201,7 +202,7 @@
     public void onTaskInfoChanged(ActivityManager.RunningTaskInfo taskInfo) {
         if (mRootTaskInfo.taskId == taskInfo.taskId) {
             // Inflates split decor view only when the root task is visible.
-            if (mRootTaskInfo.isVisible != taskInfo.isVisible) {
+            if (!ENABLE_SHELL_TRANSITIONS && mRootTaskInfo.isVisible != taskInfo.isVisible) {
                 if (taskInfo.isVisible) {
                     mSplitDecorManager.inflate(mContext, mRootLeash,
                             taskInfo.configuration.windowConfiguration.getBounds());
@@ -385,6 +386,7 @@
     void resetBounds(WindowContainerTransaction wct) {
         wct.setBounds(mRootTaskInfo.token, null);
         wct.setAppBounds(mRootTaskInfo.token, null);
+        wct.setSmallestScreenWidthDp(mRootTaskInfo.token, SMALLEST_SCREEN_WIDTH_DP_UNDEFINED);
     }
 
     void onSplitScreenListenerRegistered(SplitScreen.SplitScreenListener listener,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
index d16b497..3b306e7 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
@@ -191,6 +191,12 @@
      */
     private static final int SYNC_ALLOWANCE_MS = 120;
 
+    /**
+     * Keyguard gets a more generous timeout to finish its animations, because we are always holding
+     * a sleep token during occlude/unocclude transitions and we want them to finish playing cleanly
+     */
+    private static final int SYNC_ALLOWANCE_KEYGUARD_MS = 2000;
+
     /** For testing only. Disables the force-finish timeout on sync. */
     private boolean mDisableForceSync = false;
 
@@ -673,7 +679,7 @@
                 // Sleep starts a process of forcing all prior transitions to finish immediately
                 ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS,
                         "Start finish-for-sync track %d", i);
-                finishForSync(i, null /* forceFinish */);
+                finishForSync(active, i, null /* forceFinish */);
             }
             if (hadPreceding) {
                 return false;
@@ -1021,6 +1027,9 @@
         for (int i = 0; i < mPendingTransitions.size(); ++i) {
             if (mPendingTransitions.get(i).mToken == token) return true;
         }
+        for (int i = 0; i < mReadyDuringSync.size(); ++i) {
+            if (mReadyDuringSync.get(i).mToken == token) return true;
+        }
         for (int t = 0; t < mTracks.size(); ++t) {
             final Track tr = mTracks.get(t);
             for (int i = 0; i < tr.mReadyTransitions.size(); ++i) {
@@ -1107,10 +1116,17 @@
      *
      * This is then repeated until there are no more pending sleep transitions.
      *
+     * @param reason The SLEEP transition that triggered this round of finishes. We will continue
+     *               looping round finishing transitions as long as this is still waiting.
      * @param forceFinish When non-null, this is the transition that we last sent the SLEEP merge
      *                    signal to -- so it will be force-finished if it's still running.
      */
-    private void finishForSync(int trackIdx, @Nullable ActiveTransition forceFinish) {
+    private void finishForSync(ActiveTransition reason,
+            int trackIdx, @Nullable ActiveTransition forceFinish) {
+        if (!isTransitionKnown(reason.mToken)) {
+            Log.d(TAG, "finishForSleep: already played sync transition " + reason);
+            return;
+        }
         final Track track = mTracks.get(trackIdx);
         if (forceFinish != null) {
             final Track trk = mTracks.get(forceFinish.getTrack());
@@ -1154,8 +1170,11 @@
             if (track.mActiveTransition == playing) {
                 if (!mDisableForceSync) {
                     // Give it a short amount of time to process it before forcing.
-                    mMainExecutor.executeDelayed(() -> finishForSync(trackIdx, playing),
-                            SYNC_ALLOWANCE_MS);
+                    final int tolerance = KeyguardTransitionHandler.handles(playing.mInfo)
+                            ? SYNC_ALLOWANCE_KEYGUARD_MS
+                            : SYNC_ALLOWANCE_MS;
+                    mMainExecutor.executeDelayed(
+                            () -> finishForSync(reason, trackIdx, playing), tolerance);
                 }
                 break;
             }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/animation/FullscreenUnfoldTaskAnimator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/animation/FullscreenUnfoldTaskAnimator.java
index f81fc6f..6bba0d1 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/animation/FullscreenUnfoldTaskAnimator.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/animation/FullscreenUnfoldTaskAnimator.java
@@ -110,7 +110,7 @@
         for (int i = state.sourceSize() - 1; i >= 0; i--) {
             final InsetsSource source = state.sourceAt(i);
             if (source.getType() == WindowInsets.Type.navigationBars()
-                    && source.insetsRoundedCornerFrame()) {
+                    && source.hasFlags(InsetsSource.FLAG_INSETS_ROUNDED_CORNER)) {
                 return source;
             }
         }
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 a4cf149..21994a9 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
@@ -50,11 +50,11 @@
 import com.android.wm.shell.unfold.UnfoldAnimationController;
 import com.android.wm.shell.unfold.UnfoldBackgroundController;
 
+import dagger.Lazy;
+
 import java.util.Optional;
 import java.util.concurrent.Executor;
 
-import dagger.Lazy;
-
 /**
  * This helper class contains logic that calculates scaling and cropping parameters
  * for the folding/unfolding animation. As an input it receives TaskInfo objects and
@@ -149,7 +149,7 @@
         for (int i = state.sourceSize() - 1; i >= 0; i--) {
             final InsetsSource source = state.sourceAt(i);
             if (source.getType() == WindowInsets.Type.navigationBars()
-                    && source.insetsRoundedCornerFrame()) {
+                    && source.hasFlags(InsetsSource.FLAG_INSETS_ROUNDED_CORNER)) {
                 return source;
             }
         }
diff --git a/libs/WindowManager/Shell/tests/flicker/Android.bp b/libs/WindowManager/Shell/tests/flicker/Android.bp
index b6696c7..78a2a38 100644
--- a/libs/WindowManager/Shell/tests/flicker/Android.bp
+++ b/libs/WindowManager/Shell/tests/flicker/Android.bp
@@ -23,14 +23,33 @@
     default_applicable_licenses: ["frameworks_base_license"],
 }
 
-android_test {
-    name: "WMShellFlickerTests",
+filegroup {
+    name: "WMShellFlickerTestsBase-src",
+    srcs: ["src/com/android/wm/shell/flicker/*.kt"],
+}
+
+filegroup {
+    name: "WMShellFlickerTestsBubbles-src",
+    srcs: ["src/**/bubble/*.kt"],
+}
+
+filegroup {
+    name: "WMShellFlickerTestsPip-src",
+    srcs: ["src/**/pip/*.kt"],
+}
+
+filegroup {
+    name: "WMShellFlickerTestsSplitScreen-src",
     srcs: [
-        "src/**/*.java",
-        "src/**/*.kt",
+        "src/**/splitscreen/*.kt",
+        "src/**/splitscreen/benchmark/*.kt",
     ],
-    manifest: "AndroidManifest.xml",
-    test_config: "AndroidTest.xml",
+}
+
+java_defaults {
+    name: "WMShellFlickerTestsDefault",
+    manifest: "manifests/AndroidManifest.xml",
+    test_config_template: "AndroidTestTemplate.xml",
     platform_apis: true,
     certificate: "platform",
     optimize: {
@@ -40,16 +59,69 @@
     libs: ["android.test.runner"],
     static_libs: [
         "androidx.test.ext.junit",
+        "flickertestapplib",
         "flickerlib",
-        "flickerlib-apphelpers",
         "flickerlib-helpers",
-        "truth-prebuilt",
-        "app-helpers-core",
+        "platform-test-annotations",
+        "wm-flicker-common-app-helpers",
+        "wm-flicker-common-assertions",
         "launcher-helper-lib",
         "launcher-aosp-tapl",
-        "wm-flicker-common-assertions",
-        "wm-flicker-common-app-helpers",
-        "platform-test-annotations",
-        "flickertestapplib",
+    ],
+    data: [
+        ":FlickerTestApp",
+    ],
+}
+
+android_test {
+    name: "WMShellFlickerTestsOther",
+    defaults: ["WMShellFlickerTestsDefault"],
+    additional_manifests: ["manifests/AndroidManifestOther.xml"],
+    package_name: "com.android.wm.shell.flicker",
+    instrumentation_target_package: "com.android.wm.shell.flicker",
+    srcs: [
+        "src/**/*.java",
+        "src/**/*.kt",
+    ],
+    exclude_srcs: [
+        ":WMShellFlickerTestsBubbles-src",
+        ":WMShellFlickerTestsPip-src",
+        ":WMShellFlickerTestsSplitScreen-src",
+    ],
+}
+
+android_test {
+    name: "WMShellFlickerTestsBubbles",
+    defaults: ["WMShellFlickerTestsDefault"],
+    additional_manifests: ["manifests/AndroidManifestBubbles.xml"],
+    package_name: "com.android.wm.shell.flicker.bubbles",
+    instrumentation_target_package: "com.android.wm.shell.flicker.bubbles",
+    srcs: [
+        ":WMShellFlickerTestsBase-src",
+        ":WMShellFlickerTestsBubbles-src",
+    ],
+}
+
+android_test {
+    name: "WMShellFlickerTestsPip",
+    defaults: ["WMShellFlickerTestsDefault"],
+    additional_manifests: ["manifests/AndroidManifestPip.xml"],
+    package_name: "com.android.wm.shell.flicker.pip",
+    instrumentation_target_package: "com.android.wm.shell.flicker.pip",
+    srcs: [
+        ":WMShellFlickerTestsBase-src",
+        ":WMShellFlickerTestsPip-src",
+    ],
+}
+
+android_test {
+    name: "WMShellFlickerTestsSplitScreen",
+    defaults: ["WMShellFlickerTestsDefault"],
+    additional_manifests: ["manifests/AndroidManifestSplitScreen.xml"],
+    package_name: "com.android.wm.shell.flicker.splitscreen",
+    instrumentation_target_package: "com.android.wm.shell.flicker.splitscreen",
+    srcs: [
+        ":WMShellFlickerTestsBase-src",
+        ":WMShellFlickerTestsSplitScreen-src",
     ],
 }
diff --git a/libs/WindowManager/Shell/tests/flicker/AndroidTest.xml b/libs/WindowManager/Shell/tests/flicker/AndroidTestTemplate.xml
similarity index 80%
rename from libs/WindowManager/Shell/tests/flicker/AndroidTest.xml
rename to libs/WindowManager/Shell/tests/flicker/AndroidTestTemplate.xml
index b5937ae..8818aa2 100644
--- a/libs/WindowManager/Shell/tests/flicker/AndroidTest.xml
+++ b/libs/WindowManager/Shell/tests/flicker/AndroidTestTemplate.xml
@@ -1,8 +1,20 @@
 <?xml version="1.0" encoding="utf-8"?>
 <!--
- * Copyright 2020 Google Inc. All Rights Reserved.
- -->
-<configuration description="Runs WindowManager Shell Flicker Tests">
+  ~ Copyright (C) 2023 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+<configuration description="Runs WindowManager Shell Flicker Tests {MODULE}">
     <option name="test-tag" value="FlickerTests" />
     <target_preparer class="com.android.tradefed.targetprep.DeviceSetup">
         <!-- keeps the screen on during tests -->
@@ -36,11 +48,11 @@
     </target_preparer>
     <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
         <option name="cleanup-apks" value="true"/>
-        <option name="test-file-name" value="WMShellFlickerTests.apk"/>
+        <option name="test-file-name" value="{MODULE}.apk"/>
         <option name="test-file-name" value="FlickerTestApp.apk" />
     </target_preparer>
     <test class="com.android.tradefed.testtype.AndroidJUnitTest">
-        <option name="package" value="com.android.wm.shell.flicker"/>
+        <option name="package" value="{PACKAGE}"/>
         <option name="shell-timeout" value="6600s" />
         <option name="test-timeout" value="6000s" />
         <option name="hidden-api-checks" value="false" />
diff --git a/libs/WindowManager/Shell/tests/flicker/AndroidManifest.xml b/libs/WindowManager/Shell/tests/flicker/manifests/AndroidManifest.xml
similarity index 100%
rename from libs/WindowManager/Shell/tests/flicker/AndroidManifest.xml
rename to libs/WindowManager/Shell/tests/flicker/manifests/AndroidManifest.xml
diff --git a/libs/WindowManager/Shell/tests/flicker/manifests/AndroidManifestBubbles.xml b/libs/WindowManager/Shell/tests/flicker/manifests/AndroidManifestBubbles.xml
new file mode 100644
index 0000000..437871f
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/manifests/AndroidManifestBubbles.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.bubble">
+
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+                     android:targetPackage="com.android.wm.shell.flicker.bubble"
+                     android:label="WindowManager Flicker Tests">
+    </instrumentation>
+</manifest>
diff --git a/libs/WindowManager/Shell/tests/flicker/manifests/AndroidManifestOther.xml b/libs/WindowManager/Shell/tests/flicker/manifests/AndroidManifestOther.xml
new file mode 100644
index 0000000..cf642f6
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/manifests/AndroidManifestOther.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">
+
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+                     android:targetPackage="com.android.wm.shell.flicker"
+                     android:label="WindowManager Flicker Tests">
+    </instrumentation>
+</manifest>
diff --git a/libs/WindowManager/Shell/tests/flicker/manifests/AndroidManifestPip.xml b/libs/WindowManager/Shell/tests/flicker/manifests/AndroidManifestPip.xml
new file mode 100644
index 0000000..5a8155a
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/manifests/AndroidManifestPip.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.pip">
+
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+                     android:targetPackage="com.android.wm.shell.flicker.pip"
+                     android:label="WindowManager Flicker Tests">
+    </instrumentation>
+</manifest>
diff --git a/libs/WindowManager/Shell/tests/flicker/manifests/AndroidManifestSplitScreen.xml b/libs/WindowManager/Shell/tests/flicker/manifests/AndroidManifestSplitScreen.xml
new file mode 100644
index 0000000..887d8db
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/manifests/AndroidManifestSplitScreen.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.splitscreen">
+
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+                     android:targetPackage="com.android.wm.shell.flicker.splitscreen"
+                     android:label="WindowManager Flicker Tests">
+    </instrumentation>
+</manifest>
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java
index 9189d3d..fb17d87 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java
@@ -223,7 +223,7 @@
         doReturn(topRunningTask).when(mRecentTasks).getTopRunningTask();
         // Put the same component into a task in the background
         ActivityManager.RecentTaskInfo sameTaskInfo = new ActivityManager.RecentTaskInfo();
-        doReturn(sameTaskInfo).when(mRecentTasks).findTaskInBackground(any());
+        doReturn(sameTaskInfo).when(mRecentTasks).findTaskInBackground(any(), anyInt());
 
         mSplitScreenController.startIntent(pendingIntent, mContext.getUserId(), null,
                 SPLIT_POSITION_TOP_OR_LEFT, null);
@@ -247,7 +247,7 @@
                 SPLIT_POSITION_BOTTOM_OR_RIGHT);
         // Put the same component into a task in the background
         doReturn(new ActivityManager.RecentTaskInfo()).when(mRecentTasks)
-                .findTaskInBackground(any());
+                .findTaskInBackground(any(), anyInt());
 
         mSplitScreenController.startIntent(pendingIntent, mContext.getUserId(), null,
                 SPLIT_POSITION_TOP_OR_LEFT, null);
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTestUtils.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTestUtils.java
index ae69b3d..4e446c6 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTestUtils.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTestUtils.java
@@ -44,11 +44,15 @@
 
     static SplitLayout createMockSplitLayout() {
         final Rect dividerBounds = new Rect(48, 0, 52, 100);
+        final Rect bounds1 = new Rect(0, 0, 40, 100);
+        final Rect bounds2 = new Rect(60, 0, 100, 100);
         final SurfaceControl leash = createMockSurface();
         SplitLayout out = mock(SplitLayout.class);
         doReturn(dividerBounds).when(out).getDividerBounds();
         doReturn(dividerBounds).when(out).getRefDividerBounds();
         doReturn(leash).when(out).getDividerLeash();
+        doReturn(bounds1).when(out).getBounds1();
+        doReturn(bounds2).when(out).getBounds2();
         return out;
     }
 
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java
index 8038453..60c0e55 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java
@@ -42,6 +42,7 @@
 import static org.mockito.Mockito.doAnswer;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.spy;
 
 import android.annotation.NonNull;
 import android.app.ActivityManager;
@@ -72,6 +73,7 @@
 import com.android.wm.shell.common.ShellExecutor;
 import com.android.wm.shell.common.SyncTransactionQueue;
 import com.android.wm.shell.common.TransactionPool;
+import com.android.wm.shell.common.split.SplitDecorManager;
 import com.android.wm.shell.common.split.SplitLayout;
 import com.android.wm.shell.transition.Transitions;
 
@@ -117,13 +119,13 @@
         doReturn(mockExecutor).when(mTransitions).getAnimExecutor();
         doReturn(mock(SurfaceControl.Transaction.class)).when(mTransactionPool).acquire();
         mSplitLayout = SplitTestUtils.createMockSplitLayout();
-        mMainStage = new MainStage(mContext, mTaskOrganizer, DEFAULT_DISPLAY, mock(
+        mMainStage = spy(new MainStage(mContext, mTaskOrganizer, DEFAULT_DISPLAY, mock(
                 StageTaskListener.StageListenerCallbacks.class), mSyncQueue, mSurfaceSession,
-                mIconProvider);
+                mIconProvider));
         mMainStage.onTaskAppeared(new TestRunningTaskInfoBuilder().build(), createMockSurface());
-        mSideStage = new SideStage(mContext, mTaskOrganizer, DEFAULT_DISPLAY, mock(
+        mSideStage = spy(new SideStage(mContext, mTaskOrganizer, DEFAULT_DISPLAY, mock(
                 StageTaskListener.StageListenerCallbacks.class), mSyncQueue, mSurfaceSession,
-                mIconProvider);
+                mIconProvider));
         mSideStage.onTaskAppeared(new TestRunningTaskInfoBuilder().build(), createMockSurface());
         mStageCoordinator = new SplitTestUtils.TestStageCoordinator(mContext, DEFAULT_DISPLAY,
                 mSyncQueue, mTaskOrganizer, mMainStage, mSideStage, mDisplayController,
@@ -137,6 +139,8 @@
                 .setParentTaskId(mMainStage.mRootTaskInfo.taskId).build();
         mSideChild = new TestRunningTaskInfoBuilder()
                 .setParentTaskId(mSideStage.mRootTaskInfo.taskId).build();
+        doReturn(mock(SplitDecorManager.class)).when(mMainStage).getSplitDecorManager();
+        doReturn(mock(SplitDecorManager.class)).when(mSideStage).getSplitDecorManager();
     }
 
     @Test
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 6621ab8..66b6c62 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
@@ -68,6 +68,7 @@
 import com.android.wm.shell.common.ShellExecutor;
 import com.android.wm.shell.common.SyncTransactionQueue;
 import com.android.wm.shell.common.TransactionPool;
+import com.android.wm.shell.common.split.SplitDecorManager;
 import com.android.wm.shell.common.split.SplitLayout;
 import com.android.wm.shell.splitscreen.SplitScreen.SplitScreenListener;
 import com.android.wm.shell.sysui.ShellController;
@@ -145,6 +146,8 @@
 
         mSideStage.mRootTaskInfo = new TestRunningTaskInfoBuilder().build();
         mMainStage.mRootTaskInfo = new TestRunningTaskInfoBuilder().build();
+        doReturn(mock(SplitDecorManager.class)).when(mMainStage).getSplitDecorManager();
+        doReturn(mock(SplitDecorManager.class)).when(mSideStage).getSplitDecorManager();
     }
 
     @Test
diff --git a/packages/CarrierDefaultApp/assets/slice_purchase_test.html b/packages/CarrierDefaultApp/assets/slice_purchase_test.html
index 67d2184..d2c1c042 100644
--- a/packages/CarrierDefaultApp/assets/slice_purchase_test.html
+++ b/packages/CarrierDefaultApp/assets/slice_purchase_test.html
@@ -20,7 +20,7 @@
     <meta charset="UTF-8">
     <meta name="description" content="
     This is a HTML page that calls and verifies responses from the @JavascriptInterface functions of
-    SlicePurchaseWebInterface. Test slice purchase application behavior using ADB shell commands and
+    DataBoostWebServiceFlow. Test slice purchase application behavior using ADB shell commands and
     the APIs below:
 
     FROM TERMINAL:
@@ -65,8 +65,8 @@
     <p id="requested_capability"></p>
 
     <h2>Notify purchase successful</h2>
-    <button type="button" onclick="testNotifyPurchaseSuccessful(60000)">
-        Notify purchase successful for 1 minute
+    <button type="button" onclick="testNotifyPurchaseSuccessful()">
+        Notify purchase successful
     </button>
     <p id="purchase_successful"></p>
 
diff --git a/packages/CarrierDefaultApp/assets/slice_purchase_test.js b/packages/CarrierDefaultApp/assets/slice_purchase_test.js
index 02c4fea..84ab1f9 100644
--- a/packages/CarrierDefaultApp/assets/slice_purchase_test.js
+++ b/packages/CarrierDefaultApp/assets/slice_purchase_test.js
@@ -15,19 +15,19 @@
  */
 
 function testGetRequestedCapability() {
-    let capability = SlicePurchaseWebInterface.getRequestedCapability();
+    let capability = DataBoostWebServiceFlow.getRequestedCapability();
     document.getElementById("requested_capability").innerHTML =
             "Premium capability requested: " + capability;
 }
 
-function testNotifyPurchaseSuccessful(duration_ms_long = 0) {
-    SlicePurchaseWebInterface.notifyPurchaseSuccessful(duration_ms_long);
+function testNotifyPurchaseSuccessful() {
+    DataBoostWebServiceFlow.notifyPurchaseSuccessful();
     document.getElementById("purchase_successful").innerHTML =
-            "Notified purchase success for duration: " + duration_ms_long;
+            "Notified purchase successful.";
 }
 
 function testNotifyPurchaseFailed(failure_code = 0, failure_reason = "unknown") {
-    SlicePurchaseWebInterface.notifyPurchaseFailed(failure_code, failure_reason);
+    DataBoostWebServiceFlow.notifyPurchaseFailed(failure_code, failure_reason);
     document.getElementById("purchase_failed").innerHTML =
             "Notified purchase failed.";
 }
diff --git a/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/SlicePurchaseWebInterface.java b/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/DataBoostWebServiceFlow.java
similarity index 79%
rename from packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/SlicePurchaseWebInterface.java
rename to packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/DataBoostWebServiceFlow.java
index 8547898..0aadd31 100644
--- a/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/SlicePurchaseWebInterface.java
+++ b/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/DataBoostWebServiceFlow.java
@@ -24,13 +24,13 @@
 import com.android.phone.slice.SlicePurchaseController;
 
 /**
- * Slice purchase web interface class allowing carrier websites to send responses back to the
+ * Data boost web service flow interface allowing carrier websites to send responses back to the
  * slice purchase application using JavaScript.
  */
-public class SlicePurchaseWebInterface {
+public class DataBoostWebServiceFlow {
     @NonNull SlicePurchaseActivity mActivity;
 
-    public SlicePurchaseWebInterface(@NonNull SlicePurchaseActivity activity) {
+    public DataBoostWebServiceFlow(@NonNull SlicePurchaseActivity activity) {
         mActivity = activity;
     }
 
@@ -41,7 +41,7 @@
      * This can be called using the JavaScript below:
      * <script type="text/javascript">
      *     function getRequestedCapability(duration) {
-     *         SlicePurchaseWebInterface.getRequestedCapability();
+     *         DataBoostWebServiceFlow.getRequestedCapability();
      *     }
      * </script>
      */
@@ -57,16 +57,14 @@
      *
      * This can be called using the JavaScript below:
      * <script type="text/javascript">
-     *     function notifyPurchaseSuccessful(duration_ms_long = 0) {
-     *         SlicePurchaseWebInterface.notifyPurchaseSuccessful(duration_ms_long);
+     *     function notifyPurchaseSuccessful() {
+     *         DataBoostWebServiceFlow.notifyPurchaseSuccessful();
      *     }
      * </script>
-     *
-     * @param duration The duration for which the premium capability is purchased in milliseconds.
      */
     @JavascriptInterface
-    public void notifyPurchaseSuccessful(long duration) {
-        mActivity.onPurchaseSuccessful(duration);
+    public void notifyPurchaseSuccessful() {
+        mActivity.onPurchaseSuccessful();
     }
 
     /**
@@ -76,7 +74,7 @@
      * This can be called using the JavaScript below:
      * <script type="text/javascript">
      *     function notifyPurchaseFailed(failure_code = 0, failure_reason = "unknown") {
-     *         SlicePurchaseWebInterface.notifyPurchaseFailed();
+     *         DataBoostWebServiceFlow.notifyPurchaseFailed();
      *     }
      * </script>
      *
diff --git a/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/SlicePurchaseActivity.java b/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/SlicePurchaseActivity.java
index 946185a..d304394 100644
--- a/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/SlicePurchaseActivity.java
+++ b/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/SlicePurchaseActivity.java
@@ -32,21 +32,19 @@
 import com.android.phone.slice.SlicePurchaseController;
 
 import java.net.URL;
-import java.util.concurrent.TimeUnit;
 
 /**
  * Activity that launches when the user clicks on the performance boost notification.
  * This will open a {@link WebView} for the carrier website to allow the user to complete the
  * premium capability purchase.
  * The carrier website can get the requested premium capability using the JavaScript interface
- * method {@code SlicePurchaseWebInterface.getRequestedCapability()}.
+ * method {@code DataBoostWebServiceFlow.getRequestedCapability()}.
  * If the purchase is successful, the carrier website shall notify the slice purchase application
  * using the JavaScript interface method
- * {@code SlicePurchaseWebInterface.notifyPurchaseSuccessful(duration)}, where {@code duration} is
- * the optional duration of the performance boost.
+ * {@code DataBoostWebServiceFlow.notifyPurchaseSuccessful()}.
  * If the purchase was not successful, the carrier website shall notify the slice purchase
  * application using the JavaScript interface method
- * {@code SlicePurchaseWebInterface.notifyPurchaseFailed(code, reason)}, where {@code code} is the
+ * {@code DataBoostWebServiceFlow.notifyPurchaseFailed(code, reason)}, where {@code code} is the
  * {@link SlicePurchaseController.FailureCode} indicating the reason for failure and {@code reason}
  * is the human-readable reason for failure if the failure code is
  * {@link SlicePurchaseController#FAILURE_CODE_UNKNOWN}.
@@ -118,15 +116,11 @@
         setupWebView();
     }
 
-    protected void onPurchaseSuccessful(long duration) {
+    protected void onPurchaseSuccessful() {
         logd("onPurchaseSuccessful: Carrier website indicated successfully purchased premium "
-                + "capability " + TelephonyManager.convertPremiumCapabilityToString(mCapability)
-                + (duration > 0 ? " for " + TimeUnit.MILLISECONDS.toMinutes(duration) + " minutes."
-                : "."));
-        Intent intent = new Intent();
-        intent.putExtra(SlicePurchaseController.EXTRA_PURCHASE_DURATION, duration);
-        SlicePurchaseBroadcastReceiver.sendSlicePurchaseAppResponseWithData(mApplicationContext,
-                mIntent, SlicePurchaseController.EXTRA_INTENT_SUCCESS, intent);
+                + "capability " + TelephonyManager.convertPremiumCapabilityToString(mCapability));
+        SlicePurchaseBroadcastReceiver.sendSlicePurchaseAppResponse(
+                mIntent, SlicePurchaseController.EXTRA_INTENT_SUCCESS);
         finishAndRemoveTask();
     }
 
@@ -178,7 +172,7 @@
         //  the slice purchase application.
         mWebView.getSettings().setJavaScriptEnabled(true);
         mWebView.addJavascriptInterface(
-                new SlicePurchaseWebInterface(this), "SlicePurchaseWebInterface");
+                new DataBoostWebServiceFlow(this), "DataBoostWebServiceFlow");
 
         // Display WebView
         setContentView(mWebView);
diff --git a/packages/CarrierDefaultApp/tests/unit/src/com/android/carrierdefaultapp/SlicePurchaseActivityTest.java b/packages/CarrierDefaultApp/tests/unit/src/com/android/carrierdefaultapp/SlicePurchaseActivityTest.java
index daf0b42..e7a75e5 100644
--- a/packages/CarrierDefaultApp/tests/unit/src/com/android/carrierdefaultapp/SlicePurchaseActivityTest.java
+++ b/packages/CarrierDefaultApp/tests/unit/src/com/android/carrierdefaultapp/SlicePurchaseActivityTest.java
@@ -53,6 +53,7 @@
     private static final int PHONE_ID = 0;
 
     @Mock PendingIntent mPendingIntent;
+    @Mock PendingIntent mSuccessfulIntent;
     @Mock PendingIntent mCanceledIntent;
     @Mock CarrierConfigManager mCarrierConfigManager;
     @Mock NotificationManager mNotificationManager;
@@ -107,20 +108,18 @@
         doReturn(true).when(mCanceledIntent).isBroadcast();
         doReturn(mCanceledIntent).when(spiedIntent).getParcelableExtra(
                 eq(SlicePurchaseController.EXTRA_INTENT_CANCELED), eq(PendingIntent.class));
+        doReturn(TelephonyManager.PHONE_PROCESS_NAME).when(mSuccessfulIntent).getCreatorPackage();
+        doReturn(true).when(mSuccessfulIntent).isBroadcast();
+        doReturn(mSuccessfulIntent).when(spiedIntent).getParcelableExtra(
+                eq(SlicePurchaseController.EXTRA_INTENT_SUCCESS), eq(PendingIntent.class));
 
         mSlicePurchaseActivity = startActivity(spiedIntent, null, null);
     }
 
     @Test
     public void testOnPurchaseSuccessful() throws Exception {
-        int duration = 5 * 60 * 1000; // 5 minutes
-        int invalidDuration = -1;
-        mSlicePurchaseActivity.onPurchaseSuccessful(duration);
-        ArgumentCaptor<Intent> intentCaptor = ArgumentCaptor.forClass(Intent.class);
-        verify(mPendingIntent).send(eq(mContext), eq(0), intentCaptor.capture());
-        Intent intent = intentCaptor.getValue();
-        assertEquals(duration, intent.getLongExtra(
-                SlicePurchaseController.EXTRA_PURCHASE_DURATION, invalidDuration));
+        mSlicePurchaseActivity.onPurchaseSuccessful();
+        verify(mSuccessfulIntent).send();
     }
 
     @Test
diff --git a/packages/SettingsLib/src/com/android/settingslib/dream/DreamBackend.java b/packages/SettingsLib/src/com/android/settingslib/dream/DreamBackend.java
index c4f09ce..f911d35 100644
--- a/packages/SettingsLib/src/com/android/settingslib/dream/DreamBackend.java
+++ b/packages/SettingsLib/src/com/android/settingslib/dream/DreamBackend.java
@@ -111,6 +111,9 @@
     public static final int COMPLICATION_TYPE_SMARTSPACE = 7;
     public static final int COMPLICATION_TYPE_MEDIA_ENTRY = 8;
 
+    private static final int SCREENSAVER_HOME_CONTROLS_ENABLED_DEFAULT = 1;
+    private static final int LOCKSCREEN_SHOW_CONTROLS_DEFAULT = 0;
+
     private final Context mContext;
     private final IDreamManager mDreamManager;
     private final DreamInfoComparator mComparator;
@@ -311,8 +314,14 @@
 
     /** Gets whether home controls button is enabled on the dream */
     private boolean getHomeControlsEnabled() {
-        return Settings.Secure.getInt(mContext.getContentResolver(),
-                Settings.Secure.SCREENSAVER_HOME_CONTROLS_ENABLED, 1) == 1;
+        return Settings.Secure.getInt(
+                mContext.getContentResolver(),
+                Settings.Secure.LOCKSCREEN_SHOW_CONTROLS,
+                LOCKSCREEN_SHOW_CONTROLS_DEFAULT) == 1
+                && Settings.Secure.getInt(
+                        mContext.getContentResolver(),
+                        Settings.Secure.SCREENSAVER_HOME_CONTROLS_ENABLED,
+                        SCREENSAVER_HOME_CONTROLS_ENABLED_DEFAULT) == 1;
     }
 
     /**
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/dream/DreamBackendTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/dream/DreamBackendTest.java
index 22ec12d..2edf403 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/dream/DreamBackendTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/dream/DreamBackendTest.java
@@ -28,6 +28,7 @@
 import android.content.ContentResolver;
 import android.content.Context;
 import android.content.res.Resources;
+import android.provider.Settings;
 
 import org.junit.After;
 import org.junit.Before;
@@ -84,6 +85,7 @@
 
     @Test
     public void testComplicationsEnabledByDefault() {
+        setControlsEnabledOnLockscreen(true);
         assertThat(mBackend.getComplicationsEnabled()).isTrue();
         assertThat(mBackend.getEnabledComplications()).containsExactlyElementsIn(
                 SUPPORTED_DREAM_COMPLICATIONS_LIST);
@@ -91,6 +93,7 @@
 
     @Test
     public void testEnableComplicationExplicitly() {
+        setControlsEnabledOnLockscreen(true);
         mBackend.setComplicationsEnabled(true);
         assertThat(mBackend.getEnabledComplications()).containsExactlyElementsIn(
                 SUPPORTED_DREAM_COMPLICATIONS_LIST);
@@ -99,6 +102,7 @@
 
     @Test
     public void testDisableComplications() {
+        setControlsEnabledOnLockscreen(true);
         mBackend.setComplicationsEnabled(false);
         assertThat(mBackend.getEnabledComplications())
                 .containsExactly(COMPLICATION_TYPE_HOME_CONTROLS);
@@ -107,6 +111,7 @@
 
     @Test
     public void testHomeControlsDisabled_ComplicationsEnabled() {
+        setControlsEnabledOnLockscreen(true);
         mBackend.setComplicationsEnabled(true);
         mBackend.setHomeControlsEnabled(false);
         // Home controls should not be enabled, only date and time.
@@ -118,6 +123,7 @@
 
     @Test
     public void testHomeControlsDisabled_ComplicationsDisabled() {
+        setControlsEnabledOnLockscreen(true);
         mBackend.setComplicationsEnabled(false);
         mBackend.setHomeControlsEnabled(false);
         assertThat(mBackend.getEnabledComplications()).isEmpty();
@@ -125,9 +131,9 @@
 
     @Test
     public void testHomeControlsEnabled_ComplicationsDisabled() {
+        setControlsEnabledOnLockscreen(true);
         mBackend.setComplicationsEnabled(false);
         mBackend.setHomeControlsEnabled(true);
-        // Home controls should not be enabled, only date and time.
         final List<Integer> enabledComplications =
                 Collections.singletonList(COMPLICATION_TYPE_HOME_CONTROLS);
         assertThat(mBackend.getEnabledComplications())
@@ -136,9 +142,9 @@
 
     @Test
     public void testHomeControlsEnabled_ComplicationsEnabled() {
+        setControlsEnabledOnLockscreen(true);
         mBackend.setComplicationsEnabled(true);
         mBackend.setHomeControlsEnabled(true);
-        // Home controls should not be enabled, only date and time.
         final List<Integer> enabledComplications =
                 Arrays.asList(
                         COMPLICATION_TYPE_HOME_CONTROLS,
@@ -148,4 +154,26 @@
         assertThat(mBackend.getEnabledComplications())
                 .containsExactlyElementsIn(enabledComplications);
     }
+
+    @Test
+    public void testHomeControlsEnabled_lockscreenDisabled() {
+        setControlsEnabledOnLockscreen(false);
+        mBackend.setComplicationsEnabled(true);
+        mBackend.setHomeControlsEnabled(true);
+        // Home controls should not be enabled, only date and time.
+        final List<Integer> enabledComplications =
+                Arrays.asList(
+                        COMPLICATION_TYPE_DATE,
+                        COMPLICATION_TYPE_TIME
+                );
+        assertThat(mBackend.getEnabledComplications())
+                .containsExactlyElementsIn(enabledComplications);
+    }
+
+    private void setControlsEnabledOnLockscreen(boolean enabled) {
+        Settings.Secure.putInt(
+                mContext.getContentResolver(),
+                Settings.Secure.LOCKSCREEN_SHOW_CONTROLS,
+                enabled ? 1 : 0);
+    }
 }
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 8dd2c39..465b73e 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
@@ -272,6 +272,7 @@
             color = lockScreenColor,
             animate = isAnimationEnabled,
             duration = APPEAR_ANIM_DURATION,
+            interpolator = Interpolators.EMPHASIZED_DECELERATE,
             delay = 0,
             onAnimationEnd = null
         )
@@ -562,7 +563,7 @@
         private const val DOUBLE_LINE_FORMAT_12_HOUR = "hh\nmm"
         private const val DOUBLE_LINE_FORMAT_24_HOUR = "HH\nmm"
         private const val DOZE_ANIM_DURATION: Long = 300
-        private const val APPEAR_ANIM_DURATION: Long = 350
+        private const val APPEAR_ANIM_DURATION: Long = 833
         private const val CHARGE_ANIM_DURATION_PHASE_0: Long = 500
         private const val CHARGE_ANIM_DURATION_PHASE_1: Long = 1000
         private const val COLOR_ANIM_DURATION: Long = 400
diff --git a/packages/SystemUI/res-keyguard/values/dimens.xml b/packages/SystemUI/res-keyguard/values/dimens.xml
index cad2c16..4b79689 100644
--- a/packages/SystemUI/res-keyguard/values/dimens.xml
+++ b/packages/SystemUI/res-keyguard/values/dimens.xml
@@ -96,6 +96,7 @@
 
     <!-- additional offset for clock switch area items -->
     <dimen name="small_clock_height">114dp</dimen>
+    <dimen name="small_clock_padding_top">28dp</dimen>
     <dimen name="clock_padding_start">28dp</dimen>
     <dimen name="below_clock_padding_start">32dp</dimen>
     <dimen name="below_clock_padding_end">16dp</dimen>
diff --git a/packages/SystemUI/res/layout/volume_ringer_drawer.xml b/packages/SystemUI/res/layout/volume_ringer_drawer.xml
index 1112bcd..9b1fa23 100644
--- a/packages/SystemUI/res/layout/volume_ringer_drawer.xml
+++ b/packages/SystemUI/res/layout/volume_ringer_drawer.xml
@@ -85,7 +85,7 @@
                     android:layout_height="@dimen/volume_ringer_drawer_icon_size"
                     android:layout_gravity="center"
                     android:tint="?android:attr/textColorPrimary"
-                    android:src="@drawable/ic_volume_ringer_mute" />
+                    android:src="@drawable/ic_speaker_mute" />
 
             </FrameLayout>
 
@@ -102,7 +102,7 @@
                     android:layout_height="@dimen/volume_ringer_drawer_icon_size"
                     android:layout_gravity="center"
                     android:tint="?android:attr/textColorPrimary"
-                    android:src="@drawable/ic_volume_ringer" />
+                    android:src="@drawable/ic_speaker_on" />
 
             </FrameLayout>
 
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index a62dead..8d3ba36 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -716,7 +716,7 @@
     <!-- Minimum margin between clock and status bar -->
     <dimen name="keyguard_clock_top_margin">18dp</dimen>
     <!-- The amount to shift the clocks during a small/large transition -->
-    <dimen name="keyguard_clock_switch_y_shift">10dp</dimen>
+    <dimen name="keyguard_clock_switch_y_shift">14dp</dimen>
     <!-- When large clock is showing, offset the smartspace by this amount -->
     <dimen name="keyguard_smartspace_top_offset">12dp</dimen>
     <!-- With the large clock, move up slightly from the center -->
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java
index 7cce75f..a90a34c 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java
@@ -1,5 +1,8 @@
 package com.android.keyguard;
 
+import static android.view.View.ALPHA;
+import static android.view.View.TRANSLATION_Y;
+
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
 import android.animation.AnimatorSet;
@@ -35,11 +38,12 @@
 
     private static final String TAG = "KeyguardClockSwitch";
 
-    private static final long CLOCK_OUT_MILLIS = 150;
-    private static final long CLOCK_IN_MILLIS = 200;
-    public static final long CLOCK_IN_START_DELAY_MILLIS = CLOCK_OUT_MILLIS / 2;
-    private static final long STATUS_AREA_START_DELAY_MILLIS = 50;
-    private static final long STATUS_AREA_MOVE_MILLIS = 350;
+    private static final long CLOCK_OUT_MILLIS = 133;
+    private static final long CLOCK_IN_MILLIS = 167;
+    public static final long CLOCK_IN_START_DELAY_MILLIS = 133;
+    private static final long STATUS_AREA_START_DELAY_MILLIS = 0;
+    private static final long STATUS_AREA_MOVE_UP_MILLIS = 967;
+    private static final long STATUS_AREA_MOVE_DOWN_MILLIS = 467;
 
     @IntDef({LARGE, SMALL})
     @Retention(RetentionPolicy.SOURCE)
@@ -66,6 +70,17 @@
                 top + targetHeight);
     }
 
+    /** Returns a region for the small clock to position itself, based on the given parent. */
+    public static Rect getSmallClockRegion(ViewGroup parent) {
+        int targetHeight = parent.getResources()
+                .getDimensionPixelSize(R.dimen.small_clock_text_size);
+        return new Rect(
+                parent.getLeft(),
+                parent.getTop(),
+                parent.getRight(),
+                parent.getTop() + targetHeight);
+    }
+
     /**
      * Frame for small/large clocks
      */
@@ -90,7 +105,7 @@
 
     @VisibleForTesting AnimatorSet mClockInAnim = null;
     @VisibleForTesting AnimatorSet mClockOutAnim = null;
-    private ObjectAnimator mStatusAreaAnim = null;
+    private AnimatorSet mStatusAreaAnim = null;
 
     private int mClockSwitchYAmount;
     @VisibleForTesting boolean mChildrenAreLaidOut = false;
@@ -172,13 +187,8 @@
     void updateClockTargetRegions() {
         if (mClock != null) {
             if (mSmallClockFrame.isLaidOut()) {
-                int targetHeight =  getResources()
-                        .getDimensionPixelSize(R.dimen.small_clock_text_size);
-                mClock.getSmallClock().getEvents().onTargetRegionChanged(new Rect(
-                        mSmallClockFrame.getLeft(),
-                        mSmallClockFrame.getTop(),
-                        mSmallClockFrame.getRight(),
-                        mSmallClockFrame.getTop() + targetHeight));
+                Rect targetRegion = getSmallClockRegion(mSmallClockFrame);
+                mClock.getSmallClock().getEvents().onTargetRegionChanged(targetRegion);
             }
 
             if (mLargeClockFrame.isLaidOut()) {
@@ -220,28 +230,34 @@
         mStatusAreaAnim = null;
 
         View in, out;
-        int direction = 1;
-        float statusAreaYTranslation;
+        float statusAreaYTranslation, clockInYTranslation, clockOutYTranslation;
         if (useLargeClock) {
             out = mSmallClockFrame;
             in = mLargeClockFrame;
             if (indexOfChild(in) == -1) addView(in, 0);
-            direction = -1;
             statusAreaYTranslation = mSmallClockFrame.getTop() - mStatusArea.getTop()
                     + mSmartspaceTopOffset;
+            clockInYTranslation = 0;
+            clockOutYTranslation = 0; // Small clock translation is handled with statusArea
         } else {
             in = mSmallClockFrame;
             out = mLargeClockFrame;
             statusAreaYTranslation = 0f;
+            clockInYTranslation = 0f;
+            clockOutYTranslation = mClockSwitchYAmount * -1f;
 
-            // Must remove in order for notifications to appear in the proper place
+            // Must remove in order for notifications to appear in the proper place, ideally this
+            // would happen after the out animation runs, but we can't guarantee that the
+            // nofications won't enter only after the out animation runs.
             removeView(out);
         }
 
         if (!animate) {
             out.setAlpha(0f);
+            out.setTranslationY(clockOutYTranslation);
             out.setVisibility(INVISIBLE);
             in.setAlpha(1f);
+            in.setTranslationY(clockInYTranslation);
             in.setVisibility(VISIBLE);
             mStatusArea.setTranslationY(statusAreaYTranslation);
             return;
@@ -249,11 +265,10 @@
 
         mClockOutAnim = new AnimatorSet();
         mClockOutAnim.setDuration(CLOCK_OUT_MILLIS);
-        mClockOutAnim.setInterpolator(Interpolators.FAST_OUT_LINEAR_IN);
+        mClockOutAnim.setInterpolator(Interpolators.LINEAR);
         mClockOutAnim.playTogether(
-                ObjectAnimator.ofFloat(out, View.ALPHA, 0f),
-                ObjectAnimator.ofFloat(out, View.TRANSLATION_Y, 0,
-                        direction * -mClockSwitchYAmount));
+                ObjectAnimator.ofFloat(out, ALPHA, 0f),
+                ObjectAnimator.ofFloat(out, TRANSLATION_Y, clockOutYTranslation));
         mClockOutAnim.addListener(new AnimatorListenerAdapter() {
             public void onAnimationEnd(Animator animation) {
                 if (mClockOutAnim == animation) {
@@ -268,8 +283,9 @@
         mClockInAnim = new AnimatorSet();
         mClockInAnim.setDuration(CLOCK_IN_MILLIS);
         mClockInAnim.setInterpolator(Interpolators.LINEAR_OUT_SLOW_IN);
-        mClockInAnim.playTogether(ObjectAnimator.ofFloat(in, View.ALPHA, 1f),
-                ObjectAnimator.ofFloat(in, View.TRANSLATION_Y, direction * mClockSwitchYAmount, 0));
+        mClockInAnim.playTogether(
+                ObjectAnimator.ofFloat(in, ALPHA, 1f),
+                ObjectAnimator.ofFloat(in, TRANSLATION_Y, clockInYTranslation));
         mClockInAnim.setStartDelay(CLOCK_IN_START_DELAY_MILLIS);
         mClockInAnim.addListener(new AnimatorListenerAdapter() {
             public void onAnimationEnd(Animator animation) {
@@ -279,14 +295,14 @@
             }
         });
 
-        mClockInAnim.start();
-        mClockOutAnim.start();
-
-        mStatusAreaAnim = ObjectAnimator.ofFloat(mStatusArea, View.TRANSLATION_Y,
-                statusAreaYTranslation);
-        mStatusAreaAnim.setStartDelay(useLargeClock ? STATUS_AREA_START_DELAY_MILLIS : 0L);
-        mStatusAreaAnim.setDuration(STATUS_AREA_MOVE_MILLIS);
-        mStatusAreaAnim.setInterpolator(Interpolators.FAST_OUT_SLOW_IN);
+        mStatusAreaAnim = new AnimatorSet();
+        mStatusAreaAnim.setStartDelay(STATUS_AREA_START_DELAY_MILLIS);
+        mStatusAreaAnim.setDuration(
+                useLargeClock ? STATUS_AREA_MOVE_UP_MILLIS : STATUS_AREA_MOVE_DOWN_MILLIS);
+        mStatusAreaAnim.setInterpolator(Interpolators.EMPHASIZED);
+        mStatusAreaAnim.playTogether(
+                ObjectAnimator.ofFloat(mStatusArea, TRANSLATION_Y, statusAreaYTranslation),
+                ObjectAnimator.ofFloat(mSmallClockFrame, TRANSLATION_Y, statusAreaYTranslation));
         mStatusAreaAnim.addListener(new AnimatorListenerAdapter() {
             public void onAnimationEnd(Animator animation) {
                 if (mStatusAreaAnim == animation) {
@@ -294,6 +310,9 @@
                 }
             }
         });
+
+        mClockInAnim.start();
+        mClockOutAnim.start();
         mStatusAreaAnim.start();
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationSettings.java b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationSettings.java
index d0ff9f8..d27087d 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationSettings.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationSettings.java
@@ -79,7 +79,8 @@
     private final Runnable mWindowInsetChangeRunnable;
     private final SfVsyncFrameCallbackProvider mSfVsyncFrameProvider;
 
-    private final LayoutParams mParams;
+    @VisibleForTesting
+    final LayoutParams mParams;
     @VisibleForTesting
     final Rect mDraggableWindowBounds = new Rect();
     private boolean mIsVisible = false;
@@ -529,7 +530,6 @@
             // CONFIG_FONT_SCALE: font size change
             // CONFIG_LOCALE: language change
             // CONFIG_DENSITY: display size change
-
             mParams.accessibilityTitle = getAccessibilityWindowTitle(mContext);
 
             boolean showSettingPanelAfterConfigChange = mIsVisible;
@@ -541,16 +541,13 @@
             return;
         }
 
-        if ((configDiff & ActivityInfo.CONFIG_ORIENTATION) != 0) {
-            final Rect previousDraggableBounds = new Rect(mDraggableWindowBounds);
+        if ((configDiff & ActivityInfo.CONFIG_ORIENTATION) != 0
+                || (configDiff & ActivityInfo.CONFIG_SCREEN_SIZE) != 0) {
             mDraggableWindowBounds.set(getDraggableWindowBounds());
-            // Keep the Y position with the same height ratio before the window bounds and
-            // draggable bounds are changed.
-            final float windowHeightFraction = (float) (mParams.y - previousDraggableBounds.top)
-                    / previousDraggableBounds.height();
-            mParams.y = (int) (windowHeightFraction * mDraggableWindowBounds.height())
-                    + mDraggableWindowBounds.top;
-            return;
+            // reset the panel position to the right-bottom corner
+            mParams.x = mDraggableWindowBounds.right;
+            mParams.y = mDraggableWindowBounds.bottom;
+            updateButtonViewLayoutIfNeeded();
         }
     }
 
@@ -562,7 +559,8 @@
         mDraggableWindowBounds.set(newBounds);
     }
 
-    private void updateButtonViewLayoutIfNeeded() {
+    @VisibleForTesting
+    void updateButtonViewLayoutIfNeeded() {
         if (mIsVisible) {
             mParams.x = MathUtils.constrain(mParams.x, mDraggableWindowBounds.left,
                     mDraggableWindowBounds.right);
diff --git a/packages/SystemUI/src/com/android/systemui/authentication/data/repository/AuthenticationRepository.kt b/packages/SystemUI/src/com/android/systemui/authentication/data/repository/AuthenticationRepository.kt
index 9d9a87d..c684dc5 100644
--- a/packages/SystemUI/src/com/android/systemui/authentication/data/repository/AuthenticationRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/authentication/data/repository/AuthenticationRepository.kt
@@ -51,6 +51,12 @@
      */
     val isBypassEnabled: StateFlow<Boolean>
 
+    /**
+     * Number of consecutively failed authentication attempts. This resets to `0` when
+     * authentication succeeds.
+     */
+    val failedAuthenticationAttempts: StateFlow<Int>
+
     /** See [isUnlocked]. */
     fun setUnlocked(isUnlocked: Boolean)
 
@@ -59,6 +65,9 @@
 
     /** See [isBypassEnabled]. */
     fun setBypassEnabled(isBypassEnabled: Boolean)
+
+    /** See [failedAuthenticationAttempts]. */
+    fun setFailedAuthenticationAttempts(failedAuthenticationAttempts: Int)
 }
 
 class AuthenticationRepositoryImpl @Inject constructor() : AuthenticationRepository {
@@ -75,6 +84,10 @@
     private val _isBypassEnabled = MutableStateFlow(false)
     override val isBypassEnabled: StateFlow<Boolean> = _isBypassEnabled.asStateFlow()
 
+    private val _failedAuthenticationAttempts = MutableStateFlow(0)
+    override val failedAuthenticationAttempts: StateFlow<Int> =
+        _failedAuthenticationAttempts.asStateFlow()
+
     override fun setUnlocked(isUnlocked: Boolean) {
         _isUnlocked.value = isUnlocked
     }
@@ -86,6 +99,10 @@
     override fun setAuthenticationMethod(authenticationMethod: AuthenticationMethodModel) {
         _authenticationMethod.value = authenticationMethod
     }
+
+    override fun setFailedAuthenticationAttempts(failedAuthenticationAttempts: Int) {
+        _failedAuthenticationAttempts.value = failedAuthenticationAttempts
+    }
 }
 
 @Module
diff --git a/packages/SystemUI/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractor.kt b/packages/SystemUI/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractor.kt
index 5aea930..3984627 100644
--- a/packages/SystemUI/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractor.kt
@@ -75,6 +75,12 @@
      */
     val isBypassEnabled: StateFlow<Boolean> = repository.isBypassEnabled
 
+    /**
+     * Number of consecutively failed authentication attempts. This resets to `0` when
+     * authentication succeeds.
+     */
+    val failedAuthenticationAttempts: StateFlow<Int> = repository.failedAuthenticationAttempts
+
     init {
         // UNLOCKS WHEN AUTH METHOD REMOVED.
         //
@@ -130,7 +136,12 @@
             }
 
         if (isSuccessful) {
+            repository.setFailedAuthenticationAttempts(0)
             repository.setUnlocked(true)
+        } else {
+            repository.setFailedAuthenticationAttempts(
+                repository.failedAuthenticationAttempts.value + 1
+            )
         }
 
         return isSuccessful
diff --git a/packages/SystemUI/src/com/android/systemui/authentication/shared/model/AuthenticationMethodModel.kt b/packages/SystemUI/src/com/android/systemui/authentication/shared/model/AuthenticationMethodModel.kt
index 83250b6..6f008c3 100644
--- a/packages/SystemUI/src/com/android/systemui/authentication/shared/model/AuthenticationMethodModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/authentication/shared/model/AuthenticationMethodModel.kt
@@ -36,8 +36,10 @@
 
     data class Password(val password: String) : AuthenticationMethodModel(isSecure = true)
 
-    data class Pattern(val coordinates: List<PatternCoordinate>) :
-        AuthenticationMethodModel(isSecure = true) {
+    data class Pattern(
+        val coordinates: List<PatternCoordinate>,
+        val isPatternVisible: Boolean = true,
+    ) : AuthenticationMethodModel(isSecure = true) {
 
         data class PatternCoordinate(
             val x: Int,
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintIconController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintIconController.kt
index f04fdfff..9807b9e 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintIconController.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintIconController.kt
@@ -122,7 +122,9 @@
         if (shouldAnimateIconViewForTransition(lastState, newState)) {
             iconView.playAnimation()
         }
-        LottieColorUtils.applyDynamicColors(context, iconView)
+        if (isSideFps) {
+            LottieColorUtils.applyDynamicColors(context, iconView)
+        }
     }
 
     override fun updateIcon(@BiometricState lastState: Int, @BiometricState newState: Int) {
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/BiometricPromptLayout.java b/packages/SystemUI/src/com/android/systemui/biometrics/ui/BiometricPromptLayout.java
index 3753d10..fb246cd 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/BiometricPromptLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/BiometricPromptLayout.java
@@ -19,9 +19,12 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.content.Context;
+import android.graphics.Insets;
 import android.util.AttributeSet;
 import android.util.Log;
 import android.view.View;
+import android.view.WindowInsets;
+import android.view.WindowManager;
 import android.widget.FrameLayout;
 import android.widget.LinearLayout;
 import android.widget.TextView;
@@ -44,6 +47,8 @@
 
     private static final String TAG = "BiometricPromptLayout";
 
+    @NonNull
+    private final WindowManager mWindowManager;
     @Nullable
     private AuthController.ScaleFactorProvider mScaleFactorProvider;
     @Nullable
@@ -60,6 +65,8 @@
     public BiometricPromptLayout(Context context, AttributeSet attrs) {
         super(context, attrs);
 
+        mWindowManager = context.getSystemService(WindowManager.class);
+
         mUseCustomBpSize = getResources().getBoolean(R.bool.use_custom_bp_size);
         mCustomBpWidth = getResources().getDimensionPixelSize(R.dimen.biometric_dialog_width);
         mCustomBpHeight = getResources().getDimensionPixelSize(R.dimen.biometric_dialog_height);
@@ -144,8 +151,13 @@
             width = Math.min(width, height);
         }
 
+        // add nav bar insets since the parent AuthContainerView
+        // uses LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS
+        final Insets insets = mWindowManager.getMaximumWindowMetrics().getWindowInsets()
+                .getInsets(WindowInsets.Type.navigationBars());
         final AuthDialog.LayoutParams params = onMeasureInternal(width, height);
-        setMeasuredDimension(params.mMediumWidth, params.mMediumHeight);
+        setMeasuredDimension(params.mMediumWidth + insets.left + insets.right,
+                params.mMediumHeight + insets.bottom);
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/CredentialPasswordView.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/CredentialPasswordView.kt
index ede62ac..a3f34ce 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/CredentialPasswordView.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/CredentialPasswordView.kt
@@ -68,15 +68,15 @@
         var inputTopBound: Int
         var headerRightBound = right
         var headerTopBounds = top
+        var headerBottomBounds = bottom
         val subTitleBottom: Int = if (subtitleView.isGone) titleView.bottom else subtitleView.bottom
         val descBottom = if (descriptionView.isGone) subTitleBottom else descriptionView.bottom
         if (resources.configuration.orientation == ORIENTATION_LANDSCAPE) {
             inputTopBound = (bottom - credentialInput.height) / 2
             inputLeftBound = (right - left) / 2
             headerRightBound = inputLeftBound
-            headerTopBounds -= iconView.bottom.coerceAtMost(bottomInset)
-
-            if (descriptionView.bottom > bottomInset) {
+            if (descriptionView.bottom > headerBottomBounds) {
+                headerTopBounds -= iconView.bottom.coerceAtMost(bottomInset)
                 credentialHeader.layout(left, headerTopBounds, headerRightBound, bottom)
             }
         } else {
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/data/repo/BouncerRepository.kt b/packages/SystemUI/src/com/android/systemui/bouncer/data/repo/BouncerRepository.kt
index 4c817b2..49a0a3c 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/data/repo/BouncerRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/data/repo/BouncerRepository.kt
@@ -16,6 +16,7 @@
 
 package com.android.systemui.bouncer.data.repo
 
+import com.android.systemui.bouncer.shared.model.AuthenticationThrottledModel
 import com.android.systemui.dagger.SysUISingleton
 import javax.inject.Inject
 import kotlinx.coroutines.flow.MutableStateFlow
@@ -29,7 +30,15 @@
     /** The user-facing message to show in the bouncer. */
     val message: StateFlow<String?> = _message.asStateFlow()
 
+    private val _throttling = MutableStateFlow<AuthenticationThrottledModel?>(null)
+    /** The current authentication throttling state. If `null`, there's no throttling. */
+    val throttling: StateFlow<AuthenticationThrottledModel?> = _throttling.asStateFlow()
+
     fun setMessage(message: String?) {
         _message.value = message
     }
+
+    fun setThrottling(throttling: AuthenticationThrottledModel?) {
+        _throttling.value = throttling
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractor.kt b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractor.kt
index 8264fed..e462e2f 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractor.kt
@@ -17,10 +17,12 @@
 package com.android.systemui.bouncer.domain.interactor
 
 import android.content.Context
+import androidx.annotation.VisibleForTesting
 import com.android.systemui.R
 import com.android.systemui.authentication.domain.interactor.AuthenticationInteractor
 import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
 import com.android.systemui.bouncer.data.repo.BouncerRepository
+import com.android.systemui.bouncer.shared.model.AuthenticationThrottledModel
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.scene.domain.interactor.SceneInteractor
 import com.android.systemui.scene.shared.model.SceneKey
@@ -29,8 +31,11 @@
 import dagger.assisted.AssistedFactory
 import dagger.assisted.AssistedInject
 import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.delay
+import kotlinx.coroutines.flow.SharingStarted
 import kotlinx.coroutines.flow.StateFlow
 import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.stateIn
 import kotlinx.coroutines.launch
 
 /** Encapsulates business logic and application state accessing use-cases. */
@@ -46,7 +51,22 @@
 ) {
 
     /** The user-facing message to show in the bouncer. */
-    val message: StateFlow<String?> = repository.message
+    val message: StateFlow<String?> =
+        combine(
+                repository.message,
+                repository.throttling,
+            ) { message, throttling ->
+                messageOrThrottlingMessage(message, throttling)
+            }
+            .stateIn(
+                scope = applicationScope,
+                started = SharingStarted.WhileSubscribed(),
+                initialValue =
+                    messageOrThrottlingMessage(
+                        repository.message.value,
+                        repository.throttling.value,
+                    )
+            )
 
     /**
      * The currently-configured authentication method. This determines how the authentication
@@ -55,6 +75,9 @@
     val authenticationMethod: StateFlow<AuthenticationMethodModel> =
         authenticationInteractor.authenticationMethod
 
+    /** The current authentication throttling state. If `null`, there's no throttling. */
+    val throttling: StateFlow<AuthenticationThrottledModel?> = repository.throttling
+
     init {
         applicationScope.launch {
             combine(
@@ -129,14 +152,39 @@
     fun authenticate(
         input: List<Any>,
     ) {
+        if (repository.throttling.value != null) {
+            return
+        }
+
         val isAuthenticated = authenticationInteractor.authenticate(input)
-        if (isAuthenticated) {
-            sceneInteractor.setCurrentScene(
-                containerName = containerName,
-                scene = SceneModel(SceneKey.Gone),
-            )
-        } else {
-            repository.setMessage(errorMessage(authenticationMethod.value))
+        val failedAttempts = authenticationInteractor.failedAuthenticationAttempts.value
+        when {
+            isAuthenticated -> {
+                repository.setThrottling(null)
+                sceneInteractor.setCurrentScene(
+                    containerName = containerName,
+                    scene = SceneModel(SceneKey.Gone),
+                )
+            }
+            failedAttempts >= THROTTLE_AGGRESSIVELY_AFTER || failedAttempts % THROTTLE_EVERY == 0 ->
+                applicationScope.launch {
+                    var remainingDurationSec = THROTTLE_DURATION_SEC
+                    while (remainingDurationSec > 0) {
+                        repository.setThrottling(
+                            AuthenticationThrottledModel(
+                                failedAttemptCount = failedAttempts,
+                                totalDurationSec = THROTTLE_DURATION_SEC,
+                                remainingDurationSec = remainingDurationSec,
+                            )
+                        )
+                        remainingDurationSec--
+                        delay(1000)
+                    }
+
+                    repository.setThrottling(null)
+                    clearMessage()
+                }
+            else -> repository.setMessage(errorMessage(authenticationMethod.value))
         }
     }
 
@@ -163,10 +211,31 @@
         }
     }
 
+    private fun messageOrThrottlingMessage(
+        message: String?,
+        throttling: AuthenticationThrottledModel?,
+    ): String {
+        return when {
+            throttling != null ->
+                applicationContext.getString(
+                    com.android.internal.R.string.lockscreen_too_many_failed_attempts_countdown,
+                    throttling.remainingDurationSec,
+                )
+            message != null -> message
+            else -> ""
+        }
+    }
+
     @AssistedFactory
     interface Factory {
         fun create(
             containerName: String,
         ): BouncerInteractor
     }
+
+    companion object {
+        @VisibleForTesting const val THROTTLE_DURATION_SEC = 30
+        @VisibleForTesting const val THROTTLE_AGGRESSIVELY_AFTER = 15
+        @VisibleForTesting const val THROTTLE_EVERY = 5
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/shared/model/AuthenticationThrottledModel.kt b/packages/SystemUI/src/com/android/systemui/bouncer/shared/model/AuthenticationThrottledModel.kt
new file mode 100644
index 0000000..cbea635
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/shared/model/AuthenticationThrottledModel.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.bouncer.shared.model
+
+/**
+ * Models application state for when further authentication attempts are being throttled due to too
+ * many consecutive failed authentication attempts.
+ */
+data class AuthenticationThrottledModel(
+    /** Total number of failed attempts so far. */
+    val failedAttemptCount: Int,
+    /** Total amount of time the user has to wait before attempting again. */
+    val totalDurationSec: Int,
+    /** Remaining amount of time the user has to wait before attempting again. */
+    val remainingDurationSec: Int,
+)
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModel.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModel.kt
index ebefb78..774a559 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModel.kt
@@ -16,4 +16,14 @@
 
 package com.android.systemui.bouncer.ui.viewmodel
 
-sealed interface AuthMethodBouncerViewModel
+import kotlinx.coroutines.flow.StateFlow
+
+sealed interface AuthMethodBouncerViewModel {
+    /**
+     * Whether user input is enabled.
+     *
+     * If `false`, user input should be completely ignored in the UI as the user is "locked out" of
+     * being able to attempt to unlock the device.
+     */
+    val isInputEnabled: StateFlow<Boolean>
+}
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 c6528d0..02991bd 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
@@ -17,6 +17,7 @@
 package com.android.systemui.bouncer.ui.viewmodel
 
 import android.content.Context
+import com.android.systemui.R
 import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
 import com.android.systemui.bouncer.domain.interactor.BouncerInteractor
 import com.android.systemui.dagger.qualifiers.Application
@@ -24,10 +25,14 @@
 import dagger.assisted.AssistedFactory
 import dagger.assisted.AssistedInject
 import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.SharingStarted
 import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.flow.distinctUntilChanged
 import kotlinx.coroutines.flow.map
 import kotlinx.coroutines.flow.stateIn
+import kotlinx.coroutines.launch
 
 /** Holds UI state and handles user input on bouncer UIs. */
 class BouncerViewModel
@@ -40,16 +45,42 @@
 ) {
     private val interactor: BouncerInteractor = interactorFactory.create(containerName)
 
+    /**
+     * Whether updates to the message should be cross-animated from one message to another.
+     *
+     * If `false`, no animation should be applied, the message text should just be replaced
+     * instantly.
+     */
+    val isMessageUpdateAnimationsEnabled: StateFlow<Boolean> =
+        interactor.throttling
+            .map { it == null }
+            .stateIn(
+                scope = applicationScope,
+                started = SharingStarted.WhileSubscribed(),
+                initialValue = interactor.throttling.value == null,
+            )
+
+    private val isInputEnabled: StateFlow<Boolean> =
+        interactor.throttling
+            .map { it == null }
+            .stateIn(
+                scope = applicationScope,
+                started = SharingStarted.WhileSubscribed(),
+                initialValue = interactor.throttling.value == null,
+            )
+
     private val pin: PinBouncerViewModel by lazy {
         PinBouncerViewModel(
             applicationScope = applicationScope,
             interactor = interactor,
+            isInputEnabled = isInputEnabled,
         )
     }
 
     private val password: PasswordBouncerViewModel by lazy {
         PasswordBouncerViewModel(
             interactor = interactor,
+            isInputEnabled = isInputEnabled,
         )
     }
 
@@ -58,6 +89,7 @@
             applicationContext = applicationContext,
             applicationScope = applicationScope,
             interactor = interactor,
+            isInputEnabled = isInputEnabled,
         )
     }
 
@@ -81,11 +113,59 @@
                 initialValue = interactor.message.value ?: "",
             )
 
+    private val _throttlingDialogMessage = MutableStateFlow<String?>(null)
+    /**
+     * A message for a throttling dialog to show when the user has attempted the wrong credential
+     * too many times and now must wait a while before attempting again.
+     *
+     * If `null`, no dialog should be shown.
+     *
+     * Once the dialog is shown, the UI should call [onThrottlingDialogDismissed] when the user
+     * dismisses this dialog.
+     */
+    val throttlingDialogMessage: StateFlow<String?> = _throttlingDialogMessage.asStateFlow()
+
+    init {
+        applicationScope.launch {
+            interactor.throttling
+                .map { model ->
+                    model?.let {
+                        when (interactor.authenticationMethod.value) {
+                            is AuthenticationMethodModel.PIN ->
+                                R.string.kg_too_many_failed_pin_attempts_dialog_message
+                            is AuthenticationMethodModel.Password ->
+                                R.string.kg_too_many_failed_password_attempts_dialog_message
+                            is AuthenticationMethodModel.Pattern ->
+                                R.string.kg_too_many_failed_pattern_attempts_dialog_message
+                            else -> null
+                        }?.let { stringResourceId ->
+                            applicationContext.getString(
+                                stringResourceId,
+                                model.failedAttemptCount,
+                                model.totalDurationSec,
+                            )
+                        }
+                    }
+                }
+                .distinctUntilChanged()
+                .collect { dialogMessageOrNull ->
+                    if (dialogMessageOrNull != null) {
+                        _throttlingDialogMessage.value = dialogMessageOrNull
+                    }
+                }
+        }
+    }
+
     /** Notifies that the emergency services button was clicked. */
     fun onEmergencyServicesButtonClicked() {
         // TODO(b/280877228): implement this
     }
 
+    /** Notifies that a throttling dialog has been dismissed by the user. */
+    fun onThrottlingDialogDismissed() {
+        _throttlingDialogMessage.value = null
+    }
+
     private fun toViewModel(
         authMethod: AuthenticationMethodModel,
     ): AuthMethodBouncerViewModel? {
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModel.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModel.kt
index 730d4e8..c38fcaa 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModel.kt
@@ -24,6 +24,7 @@
 /** Holds UI state and handles user input for the password bouncer UI. */
 class PasswordBouncerViewModel(
     private val interactor: BouncerInteractor,
+    override val isInputEnabled: StateFlow<Boolean>,
 ) : AuthMethodBouncerViewModel {
 
     private val _password = MutableStateFlow("")
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModel.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModel.kt
index eb1b457..1b0b38e 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModel.kt
@@ -37,6 +37,7 @@
     private val applicationContext: Context,
     applicationScope: CoroutineScope,
     private val interactor: BouncerInteractor,
+    override val isInputEnabled: StateFlow<Boolean>,
 ) : AuthMethodBouncerViewModel {
 
     /** The number of columns in the dot grid. */
@@ -63,6 +64,16 @@
     /** All dots on the grid. */
     val dots: StateFlow<List<PatternDotViewModel>> = _dots.asStateFlow()
 
+    /** Whether the pattern itself should be rendered visibly. */
+    val isPatternVisible: StateFlow<Boolean> =
+        interactor.authenticationMethod
+            .map { authMethod -> isPatternVisible(authMethod) }
+            .stateIn(
+                scope = applicationScope,
+                started = SharingStarted.Eagerly,
+                initialValue = isPatternVisible(interactor.authenticationMethod.value),
+            )
+
     /** Notifies that the UI has been shown to the user. */
     fun onShown() {
         interactor.resetMessage()
@@ -146,6 +157,10 @@
         _selectedDots.value = linkedSetOf()
     }
 
+    private fun isPatternVisible(authMethodModel: AuthenticationMethodModel): Boolean {
+        return (authMethodModel as? AuthenticationMethodModel.Pattern)?.isPatternVisible ?: false
+    }
+
     private fun defaultDots(): List<PatternDotViewModel> {
         return buildList {
             (0 until columnCount).forEach { x ->
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 f9223cb..2a733d9 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
@@ -33,6 +33,7 @@
 class PinBouncerViewModel(
     private val applicationScope: CoroutineScope,
     private val interactor: BouncerInteractor,
+    override val isInputEnabled: StateFlow<Boolean>,
 ) : AuthMethodBouncerViewModel {
 
     private val entered = MutableStateFlow<List<Int>>(emptyList())
diff --git a/packages/SystemUI/src/com/android/systemui/complication/ComplicationTypesUpdater.java b/packages/SystemUI/src/com/android/systemui/complication/ComplicationTypesUpdater.java
index a334c1e..0bdc7f1 100644
--- a/packages/SystemUI/src/com/android/systemui/complication/ComplicationTypesUpdater.java
+++ b/packages/SystemUI/src/com/android/systemui/complication/ComplicationTypesUpdater.java
@@ -77,6 +77,10 @@
                 Settings.Secure.SCREENSAVER_HOME_CONTROLS_ENABLED,
                 settingsObserver,
                 UserHandle.myUserId());
+        mSecureSettings.registerContentObserverForUser(
+                Settings.Secure.LOCKSCREEN_SHOW_CONTROLS,
+                settingsObserver,
+                UserHandle.myUserId());
         settingsObserver.onChange(false);
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
index ee23c51..879238d 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
@@ -100,6 +100,16 @@
     val SENSITIVE_REVEAL_ANIM =
         unreleasedFlag(268005230, "sensitive_reveal_anim", teamfood = true)
 
+    // TODO(b/280783617): Tracking Bug
+    @Keep
+    @JvmField
+    val BUILDER_EXTRAS_OVERRIDE =
+            sysPropBooleanFlag(
+                    128,
+                    "persist.sysui.notification.builder_extras_override",
+                    default = false
+            )
+
     // 200 - keyguard/lockscreen
     // ** Flag retired **
     // public static final BooleanFlag KEYGUARD_LAYOUT =
@@ -132,7 +142,7 @@
      * the digits when the clock moves.
      */
     @JvmField
-    val STEP_CLOCK_ANIMATION = unreleasedFlag(212, "step_clock_animation", teamfood = true)
+    val STEP_CLOCK_ANIMATION = releasedFlag(212, "step_clock_animation")
 
     /**
      * Migration from the legacy isDozing/dozeAmount paths to the new KeyguardTransitionRepository
@@ -237,7 +247,7 @@
     /** Whether to delay showing bouncer UI when face auth or active unlock are enrolled. */
     // TODO(b/279794160): Tracking bug.
     @JvmField
-    val DELAY_BOUNCER = releasedFlag(235, "delay_bouncer")
+    val DELAY_BOUNCER = unreleasedFlag(235, "delay_bouncer")
 
     /** Migrate the indication area to the new keyguard root view. */
     // TODO(b/280067944): Tracking bug.
@@ -521,13 +531,6 @@
     val ENABLE_PIP_APP_ICON_OVERLAY =
         sysPropBooleanFlag(1115, "persist.wm.debug.enable_pip_app_icon_overlay", default = true)
 
-    // TODO(b/272110828): Tracking bug
-    @Keep
-    @JvmField
-    val ENABLE_MOVE_FLOATING_WINDOW_IN_TABLETOP =
-        sysPropBooleanFlag(
-            1116, "persist.wm.debug.enable_move_floating_window_in_tabletop", default = true)
-
     // TODO(b/273443374): Tracking Bug
     @Keep
     @JvmField val LOCKSCREEN_LIVE_WALLPAPER =
@@ -611,8 +614,6 @@
         unreleasedFlag(1401, "quick_tap_flow_framework", teamfood = false)
 
     // 1500 - chooser aka sharesheet
-    // TODO(b/254512507): Tracking Bug
-    val CHOOSER_UNBUNDLED = releasedFlag(1500, "chooser_unbundled")
 
     // 1700 - clipboard
     @JvmField val CLIPBOARD_REMOTE_BEHAVIOR = releasedFlag(1701, "clipboard_remote_behavior")
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java
index 5d3f5f2..51a29b0 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java
@@ -19,7 +19,6 @@
 import static android.content.pm.PackageManager.PERMISSION_GRANTED;
 import static android.view.RemoteAnimationTarget.MODE_CLOSING;
 import static android.view.RemoteAnimationTarget.MODE_OPENING;
-import static android.view.WindowManager.TRANSIT_CLOSE;
 import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY;
 import static android.view.WindowManager.TRANSIT_KEYGUARD_GOING_AWAY;
 import static android.view.WindowManager.TRANSIT_KEYGUARD_OCCLUDE;
@@ -30,13 +29,11 @@
 import static android.view.WindowManager.TRANSIT_OLD_KEYGUARD_OCCLUDE_BY_DREAM;
 import static android.view.WindowManager.TRANSIT_OLD_KEYGUARD_UNOCCLUDE;
 import static android.view.WindowManager.TRANSIT_OLD_NONE;
-import static android.view.WindowManager.TRANSIT_OPEN;
-import static android.view.WindowManager.TRANSIT_TO_BACK;
-import static android.view.WindowManager.TRANSIT_TO_FRONT;
 import static android.view.WindowManager.TransitionFlags;
 import static android.view.WindowManager.TransitionOldType;
 import static android.view.WindowManager.TransitionType;
 
+import android.annotation.NonNull;
 import android.app.ActivityManager;
 import android.app.ActivityTaskManager;
 import android.app.Service;
@@ -116,6 +113,14 @@
             final ActivityManager.RunningTaskInfo taskInfo = change.getTaskInfo();
             final int taskId = taskInfo != null ? change.getTaskInfo().taskId : -1;
 
+            if (taskId != -1 && change.getParent() != null) {
+                final TransitionInfo.Change parentChange = info.getChange(change.getParent());
+                if (parentChange != null && parentChange.getTaskInfo() != null) {
+                    // Only adding the root task as the animation target.
+                    continue;
+                }
+            }
+
             final RemoteAnimationTarget target = TransitionUtil.newTarget(change,
                     // wallpapers go into the "below" layer space
                     info.getChanges().size() - i,
@@ -123,13 +128,6 @@
                     (change.getFlags() & TransitionInfo.FLAG_SHOW_WALLPAPER) != 0,
                     info, t, leashMap);
 
-            // Use hasAnimatingParent to mark the anything below root task
-            if (taskId != -1 && change.getParent() != null) {
-                final TransitionInfo.Change parentChange = info.getChange(change.getParent());
-                if (parentChange != null && parentChange.getTaskInfo() != null) {
-                    target.hasAnimatingParent = true;
-                }
-            }
             out.add(target);
         }
         return out.toArray(new RemoteAnimationTarget[out.size()]);
@@ -173,18 +171,15 @@
                         wrap(info, true /* wallpapers */, t, mLeashMap);
                 final RemoteAnimationTarget[] nonApps = new RemoteAnimationTarget[0];
 
-                // Sets the alpha to 0 for the opening root task for fade in animation. And since
-                // the fade in animation can only apply on the first opening app, so set alpha to 1
-                // for anything else.
-                for (RemoteAnimationTarget target : apps) {
-                    if (target.taskId != -1
-                            && target.mode == RemoteAnimationTarget.MODE_OPENING
-                            && !target.hasAnimatingParent) {
-                        t.setAlpha(target.leash, 0.0f);
-                    } else {
-                        t.setAlpha(target.leash, 1.0f);
+                // Set alpha back to 1 for the independent changes because we will be animating
+                // children instead.
+                for (TransitionInfo.Change chg : info.getChanges()) {
+                    if (TransitionInfo.isIndependent(chg, info)) {
+                        t.setAlpha(chg.getLeash(), 1.f);
                     }
                 }
+                initAlphaForAnimationTargets(t, apps);
+                initAlphaForAnimationTargets(t, wallpapers);
                 t.apply();
                 synchronized (mFinishCallbacks) {
                     mFinishCallbacks.put(transition, finishCallback);
@@ -223,6 +218,14 @@
                     // nothing, we'll just let it finish on its own I guess.
                 }
             }
+
+            private static void initAlphaForAnimationTargets(@NonNull SurfaceControl.Transaction t,
+                    @NonNull RemoteAnimationTarget[] targets) {
+                for (RemoteAnimationTarget target : targets) {
+                    if (target.mode != MODE_OPENING) continue;
+                    t.setAlpha(target.leash, 0.f);
+                }
+            }
         };
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt
index f96f337..122e259 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt
@@ -812,8 +812,8 @@
 
             // Translate up from the bottom.
             surfaceBehindMatrix.setTranslate(
-                    surfaceBehindRemoteAnimationTarget.localBounds.left.toFloat(),
-                    surfaceBehindRemoteAnimationTarget.localBounds.top.toFloat() +
+                    surfaceBehindRemoteAnimationTarget.screenSpaceBounds.left.toFloat(),
+                    surfaceBehindRemoteAnimationTarget.screenSpaceBounds.top.toFloat() +
                             surfaceHeight * SURFACE_BEHIND_START_TRANSLATION_Y * (1f - amount)
             )
 
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ResourceTrimmer.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ResourceTrimmer.kt
index d8affa4..1978b3d 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ResourceTrimmer.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ResourceTrimmer.kt
@@ -139,7 +139,8 @@
         if (dozeDisabledAndScreenOff || dozeEnabledAndDozeAnimationCompleted) {
             Trace.beginSection("ResourceTrimmer#trimMemory")
             Log.d(LOG_TAG, "SysUI asleep, trimming memory.")
-            globalWindowManager.trimMemory(ComponentCallbacks2.TRIM_MEMORY_BACKGROUND)
+            globalWindowManager.trimMemory(ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN)
+            globalWindowManager.trimCaches(HardwareRenderer.CACHE_TRIM_ALL)
             Trace.endSection()
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardClockRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardClockRepository.kt
new file mode 100644
index 0000000..641e20b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardClockRepository.kt
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.data.repository
+
+import android.os.UserHandle
+import android.provider.Settings
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.keyguard.shared.model.SettingsClockSize
+import com.android.systemui.util.settings.SecureSettings
+import com.android.systemui.util.settings.SettingsProxyExt.observerFlow
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.onStart
+import kotlinx.coroutines.withContext
+
+@SysUISingleton
+class KeyguardClockRepository
+@Inject
+constructor(
+    private val secureSettings: SecureSettings,
+    @Background private val backgroundDispatcher: CoroutineDispatcher,
+) {
+
+    val selectedClockSize: Flow<SettingsClockSize> =
+        secureSettings
+            .observerFlow(
+                names = arrayOf(Settings.Secure.LOCKSCREEN_USE_DOUBLE_LINE_CLOCK),
+                userId = UserHandle.USER_SYSTEM,
+            )
+            .onStart { emit(Unit) } // Forces an initial update.
+            .map { getClockSize() }
+
+    private suspend fun getClockSize(): SettingsClockSize {
+        return withContext(backgroundDispatcher) {
+            if (
+                secureSettings.getIntForUser(
+                    Settings.Secure.LOCKSCREEN_USE_DOUBLE_LINE_CLOCK,
+                    1,
+                    UserHandle.USER_CURRENT
+                ) == 1
+            ) {
+                SettingsClockSize.DYNAMIC
+            } else {
+                SettingsClockSize.SMALL
+            }
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardClockInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardClockInteractor.kt
new file mode 100644
index 0000000..98f445c
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardClockInteractor.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.systemui.keyguard.domain.interactor
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.keyguard.data.repository.KeyguardClockRepository
+import com.android.systemui.keyguard.shared.model.SettingsClockSize
+import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+
+/** Encapsulates business-logic related to the keyguard clock. */
+@SysUISingleton
+class KeyguardClockInteractor
+@Inject
+constructor(
+    repository: KeyguardClockRepository,
+) {
+    val selectedClockSize: Flow<SettingsClockSize> = repository.selectedClockSize
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/SettingsClockSize.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/SettingsClockSize.kt
new file mode 100644
index 0000000..c6b0f58
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/SettingsClockSize.kt
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package com.android.systemui.keyguard.shared.model
+
+enum class SettingsClockSize {
+    DYNAMIC,
+    SMALL,
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardPreviewClockSmartspaceViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardPreviewClockSmartspaceViewBinder.kt
new file mode 100644
index 0000000..57c32b3
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardPreviewClockSmartspaceViewBinder.kt
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package com.android.systemui.keyguard.ui.binder
+
+import android.view.View
+import androidx.core.view.isVisible
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.repeatOnLifecycle
+import com.android.systemui.keyguard.ui.viewmodel.KeyguardPreviewClockSmartspaceViewModel
+import com.android.systemui.lifecycle.repeatWhenAttached
+import kotlinx.coroutines.flow.collect
+
+/** Binder for the small clock view, large clock view and smartspace. */
+object KeyguardPreviewClockSmartspaceViewBinder {
+
+    @JvmStatic
+    fun bind(
+        largeClockHostView: View,
+        smallClockHostView: View,
+        smartspace: View?,
+        viewModel: KeyguardPreviewClockSmartspaceViewModel,
+    ) {
+        largeClockHostView.repeatWhenAttached {
+            repeatOnLifecycle(Lifecycle.State.STARTED) {
+                viewModel.isLargeClockVisible.collect { largeClockHostView.isVisible = it }
+            }
+        }
+
+        smallClockHostView.repeatWhenAttached {
+            repeatOnLifecycle(Lifecycle.State.STARTED) {
+                viewModel.isSmallClockVisible.collect { smallClockHostView.isVisible = it }
+            }
+        }
+
+        smartspace?.repeatWhenAttached {
+            repeatOnLifecycle(Lifecycle.State.STARTED) {
+                viewModel.smartSpaceTopPadding.collect { smartspace.setTopPadding(it) }
+            }
+        }
+    }
+
+    private fun View.setTopPadding(padding: Int) {
+        setPaddingRelative(paddingStart, padding, paddingEnd, paddingBottom)
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt
index 555a09b..4308d84 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt
@@ -22,6 +22,7 @@
 import android.content.Context
 import android.content.Intent
 import android.content.IntentFilter
+import android.content.res.Resources
 import android.graphics.Rect
 import android.hardware.display.DisplayManager
 import android.os.Bundle
@@ -33,6 +34,7 @@
 import android.view.ViewGroup
 import android.view.WindowManager
 import android.widget.FrameLayout
+import androidx.core.view.isInvisible
 import com.android.keyguard.ClockEventController
 import com.android.keyguard.KeyguardClockSwitch
 import com.android.systemui.R
@@ -40,7 +42,10 @@
 import com.android.systemui.broadcast.BroadcastDispatcher
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.keyguard.ui.binder.KeyguardPreviewClockSmartspaceViewBinder
 import com.android.systemui.keyguard.ui.viewmodel.KeyguardBottomAreaViewModel
+import com.android.systemui.keyguard.ui.viewmodel.KeyguardPreviewClockSmartspaceViewModel
+import com.android.systemui.plugins.ClockController
 import com.android.systemui.shared.clocks.ClockRegistry
 import com.android.systemui.shared.clocks.DefaultClockController
 import com.android.systemui.shared.clocks.shared.model.ClockPreviewConstants
@@ -60,6 +65,7 @@
     @Application private val context: Context,
     @Main private val mainDispatcher: CoroutineDispatcher,
     @Main private val mainHandler: Handler,
+    private val clockSmartspaceViewModel: KeyguardPreviewClockSmartspaceViewModel,
     private val bottomAreaViewModel: KeyguardBottomAreaViewModel,
     displayManager: DisplayManager,
     private val windowManager: WindowManager,
@@ -79,6 +85,7 @@
             KeyguardPreviewConstants.KEY_HIGHLIGHT_QUICK_AFFORDANCES,
             false,
         )
+    /** [shouldHideClock] here means that we never create and bind the clock views */
     private val shouldHideClock: Boolean =
         bundle.getBoolean(ClockPreviewConstants.KEY_HIDE_CLOCK, false)
 
@@ -87,7 +94,8 @@
     val surfacePackage: SurfaceControlViewHost.SurfacePackage
         get() = host.surfacePackage
 
-    private var clockView: View? = null
+    private lateinit var largeClockHostView: FrameLayout
+    private lateinit var smallClockHostView: FrameLayout
     private var smartSpaceView: View? = null
     private var colorOverride: Int? = null
 
@@ -126,6 +134,12 @@
 
             if (!shouldHideClock) {
                 setUpClock(rootView)
+                KeyguardPreviewClockSmartspaceViewBinder.bind(
+                    largeClockHostView,
+                    smallClockHostView,
+                    smartSpaceView,
+                    clockSmartspaceViewModel,
+                )
             }
 
             rootView.measure(
@@ -205,11 +219,9 @@
         smartSpaceView = lockscreenSmartspaceController.buildAndConnectDateView(parentView)
 
         val topPadding: Int =
-            with(context.resources) {
-                getDimensionPixelSize(R.dimen.status_bar_header_height_keyguard) +
-                    getDimensionPixelSize(R.dimen.keyguard_smartspace_top_offset) +
-                    getDimensionPixelSize(R.dimen.keyguard_clock_top_margin)
-            }
+            KeyguardPreviewClockSmartspaceViewModel.getLargeClockSmartspaceTopPadding(
+                context.resources
+            )
 
         val startPadding: Int =
             with(context.resources) {
@@ -284,10 +296,19 @@
     }
 
     private fun setUpClock(parentView: ViewGroup) {
+        largeClockHostView = createLargeClockHostView()
+        largeClockHostView.isInvisible = true
+        parentView.addView(largeClockHostView)
+
+        smallClockHostView = createSmallClockHostView(parentView.resources)
+        smallClockHostView.isInvisible = true
+        parentView.addView(smallClockHostView)
+
+        // TODO (b/283465254): Move the listeners to KeyguardClockRepository
         val clockChangeListener =
             object : ClockRegistry.ClockChangeListener {
                 override fun onCurrentClockChanged() {
-                    onClockChanged(parentView)
+                    onClockChanged()
                 }
             }
         clockRegistry.registerClockChangeListener(clockChangeListener)
@@ -317,62 +338,89 @@
         disposables.add(DisposableHandle { broadcastDispatcher.unregisterReceiver(receiver) })
 
         val layoutChangeListener =
-            object : View.OnLayoutChangeListener {
-                override fun onLayoutChange(
-                    v: View,
-                    left: Int,
-                    top: Int,
-                    right: Int,
-                    bottom: Int,
-                    oldLeft: Int,
-                    oldTop: Int,
-                    oldRight: Int,
-                    oldBottom: Int
-                ) {
-                    if (clockController.clock !is DefaultClockController) {
-                        clockController.clock
-                            ?.largeClock
-                            ?.events
-                            ?.onTargetRegionChanged(
-                                KeyguardClockSwitch.getLargeClockRegion(parentView)
-                            )
-                    }
+            View.OnLayoutChangeListener { _, _, _, _, _, _, _, _, _ ->
+                if (clockController.clock !is DefaultClockController) {
+                    clockController.clock
+                        ?.largeClock
+                        ?.events
+                        ?.onTargetRegionChanged(KeyguardClockSwitch.getLargeClockRegion(parentView))
                 }
             }
-
         parentView.addOnLayoutChangeListener(layoutChangeListener)
-
         disposables.add(
             DisposableHandle { parentView.removeOnLayoutChangeListener(layoutChangeListener) }
         )
 
-        onClockChanged(parentView)
+        onClockChanged()
     }
 
-    private fun onClockChanged(parentView: ViewGroup) {
+    private fun createLargeClockHostView(): FrameLayout {
+        val hostView = FrameLayout(context)
+        hostView.layoutParams =
+            FrameLayout.LayoutParams(
+                FrameLayout.LayoutParams.MATCH_PARENT,
+                FrameLayout.LayoutParams.MATCH_PARENT,
+            )
+        return hostView
+    }
+
+    private fun createSmallClockHostView(resources: Resources): FrameLayout {
+        val hostView = FrameLayout(context)
+        val layoutParams =
+            FrameLayout.LayoutParams(
+                FrameLayout.LayoutParams.WRAP_CONTENT,
+                resources.getDimensionPixelSize(R.dimen.small_clock_height)
+            )
+        layoutParams.topMargin =
+            KeyguardPreviewClockSmartspaceViewModel.getStatusBarHeight(resources) +
+                resources.getDimensionPixelSize(R.dimen.small_clock_padding_top)
+        hostView.layoutParams = layoutParams
+
+        hostView.setPaddingRelative(
+            resources.getDimensionPixelSize(R.dimen.clock_padding_start),
+            0,
+            0,
+            0
+        )
+        hostView.clipChildren = false
+        return hostView
+    }
+
+    private fun onClockChanged() {
         val clock = clockRegistry.createCurrentClock()
         clockController.clock = clock
 
         colorOverride?.let { clock.events.onSeedColorChanged(it) }
 
-        clock.largeClock.events.onTargetRegionChanged(
-            KeyguardClockSwitch.getLargeClockRegion(parentView)
-        )
-
-        clockView?.let { parentView.removeView(it) }
-        clockView =
-            clock.largeClock.view.apply {
-                if (shouldHighlightSelectedAffordance) {
-                    alpha = DIM_ALPHA
-                }
-                parentView.addView(this)
-                visibility = View.VISIBLE
-            }
+        updateLargeClock(clock)
+        updateSmallClock(clock)
 
         // Hide smart space if the clock has weather display; otherwise show it
         hideSmartspace(clock.largeClock.config.hasCustomWeatherDataDisplay)
     }
 
+    private fun updateLargeClock(clock: ClockController) {
+        clock.largeClock.events.onTargetRegionChanged(
+            KeyguardClockSwitch.getLargeClockRegion(largeClockHostView)
+        )
+        if (shouldHighlightSelectedAffordance) {
+            clock.largeClock.view.alpha = DIM_ALPHA
+        }
+        largeClockHostView.removeAllViews()
+        largeClockHostView.addView(clock.largeClock.view)
+    }
+
+    private fun updateSmallClock(clock: ClockController) {
+        clock.smallClock.events.onTargetRegionChanged(
+            KeyguardClockSwitch.getSmallClockRegion(smallClockHostView)
+        )
+        if (shouldHighlightSelectedAffordance) {
+            clock.smallClock.view.alpha = DIM_ALPHA
+        }
+        smallClockHostView.removeAllViews()
+        smallClockHostView.addView(clock.smallClock.view)
+    }
+
     companion object {
         private const val KEY_HOST_TOKEN = "host_token"
         private const val KEY_VIEW_WIDTH = "width"
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardPreviewClockSmartspaceViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardPreviewClockSmartspaceViewModel.kt
new file mode 100644
index 0000000..00c603b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardPreviewClockSmartspaceViewModel.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.keyguard.ui.viewmodel
+
+import android.content.Context
+import android.content.res.Resources
+import com.android.systemui.R
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.keyguard.domain.interactor.KeyguardClockInteractor
+import com.android.systemui.keyguard.shared.model.SettingsClockSize
+import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.map
+
+/** View model for the small clock view, large clock view and smartspace. */
+class KeyguardPreviewClockSmartspaceViewModel
+@Inject
+constructor(
+    @Application private val context: Context,
+    interactor: KeyguardClockInteractor,
+) {
+
+    val isLargeClockVisible: Flow<Boolean> =
+        interactor.selectedClockSize.map { it == SettingsClockSize.DYNAMIC }
+
+    val isSmallClockVisible: Flow<Boolean> =
+        interactor.selectedClockSize.map { it == SettingsClockSize.SMALL }
+
+    val smartSpaceTopPadding: Flow<Int> =
+        interactor.selectedClockSize.map {
+            when (it) {
+                SettingsClockSize.DYNAMIC -> getLargeClockSmartspaceTopPadding(context.resources)
+                SettingsClockSize.SMALL -> getSmallClockSmartspaceTopPadding(context.resources)
+            }
+        }
+
+    companion object {
+        fun getLargeClockSmartspaceTopPadding(resources: Resources): Int {
+            return with(resources) {
+                getDimensionPixelSize(R.dimen.status_bar_header_height_keyguard) +
+                    getDimensionPixelSize(R.dimen.keyguard_smartspace_top_offset) +
+                    getDimensionPixelSize(R.dimen.keyguard_clock_top_margin)
+            }
+        }
+
+        fun getSmallClockSmartspaceTopPadding(resources: Resources): Int {
+            return with(resources) {
+                getStatusBarHeight(this) +
+                    getDimensionPixelSize(R.dimen.small_clock_padding_top) +
+                    getDimensionPixelSize(R.dimen.small_clock_height)
+            }
+        }
+
+        fun getStatusBarHeight(resource: Resources): Int {
+            var result = 0
+            val resourceId: Int = resource.getIdentifier("status_bar_height", "dimen", "android")
+            if (resourceId > 0) {
+                result = resource.getDimensionPixelSize(resourceId)
+            }
+            return result
+        }
+    }
+}
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 5079487..bce3346 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
@@ -16,10 +16,12 @@
 
 package com.android.systemui.media.controls.pipeline
 
+import android.annotation.SuppressLint
 import android.app.BroadcastOptions
 import android.app.Notification
 import android.app.Notification.EXTRA_SUBSTITUTE_APP_NAME
 import android.app.PendingIntent
+import android.app.StatusBarManager
 import android.app.smartspace.SmartspaceConfig
 import android.app.smartspace.SmartspaceManager
 import android.app.smartspace.SmartspaceSession
@@ -43,7 +45,6 @@
 import android.net.Uri
 import android.os.Parcelable
 import android.os.Process
-import android.os.RemoteException
 import android.os.UserHandle
 import android.provider.Settings
 import android.service.notification.StatusBarNotification
@@ -53,7 +54,6 @@
 import android.util.Pair as APair
 import androidx.media.utils.MediaConstants
 import com.android.internal.logging.InstanceId
-import com.android.internal.statusbar.IStatusBarService
 import com.android.keyguard.KeyguardUpdateMonitor
 import com.android.systemui.Dumpable
 import com.android.systemui.R
@@ -185,7 +185,6 @@
     private val logger: MediaUiEventLogger,
     private val smartspaceManager: SmartspaceManager,
     private val keyguardUpdateMonitor: KeyguardUpdateMonitor,
-    private val statusBarService: IStatusBarService,
 ) : Dumpable, BcSmartspaceDataPlugin.SmartspaceTargetListener {
 
     companion object {
@@ -230,6 +229,10 @@
     private val artworkHeight =
         context.resources.getDimensionPixelSize(R.dimen.qs_media_session_height_expanded)
 
+    @SuppressLint("WrongConstant") // sysui allowed to call STATUS_BAR_SERVICE
+    private val statusBarManager =
+        context.getSystemService(Context.STATUS_BAR_SERVICE) as StatusBarManager
+
     /** Check whether this notification is an RCN */
     private fun isRemoteCastNotification(sbn: StatusBarNotification): Boolean {
         return sbn.notification.extras.containsKey(Notification.EXTRA_MEDIA_REMOTE_DEVICE)
@@ -257,7 +260,6 @@
         mediaFlags: MediaFlags,
         logger: MediaUiEventLogger,
         smartspaceManager: SmartspaceManager,
-        statusBarService: IStatusBarService,
         keyguardUpdateMonitor: KeyguardUpdateMonitor,
     ) : this(
         context,
@@ -283,7 +285,6 @@
         logger,
         smartspaceManager,
         keyguardUpdateMonitor,
-        statusBarService,
     )
 
     private val appChangeReceiver =
@@ -786,34 +787,19 @@
 
         // Song name
         var song: CharSequence? = metadata?.getString(MediaMetadata.METADATA_KEY_DISPLAY_TITLE)
-        if (song == null) {
+        if (song.isNullOrBlank()) {
             song = metadata?.getString(MediaMetadata.METADATA_KEY_TITLE)
         }
-        if (song == null) {
+        if (song.isNullOrBlank()) {
             song = HybridGroupManager.resolveTitle(notif)
         }
         if (song.isNullOrBlank()) {
-            if (mediaFlags.isMediaTitleRequired(sbn.packageName, sbn.user)) {
-                // App is required to provide a title: cancel the underlying notification
-                try {
-                    statusBarService.onNotificationError(
-                        sbn.packageName,
-                        sbn.tag,
-                        sbn.id,
-                        sbn.uid,
-                        sbn.initialPid,
-                        MEDIA_TITLE_ERROR_MESSAGE,
-                        sbn.user.identifier
-                    )
-                } catch (e: RemoteException) {
-                    Log.e(TAG, "cancelNotification failed: $e")
-                }
-                // Only add log for media removed if active media is updated with invalid title.
-                foregroundExecutor.execute { removeEntry(key, !isNewlyActiveEntry) }
-                return
-            } else {
-                // For apps that don't have the title requirement yet, add a placeholder
-                song = context.getString(R.string.controls_media_empty_title, appName)
+            // For apps that don't include a title, log and add a placeholder
+            song = context.getString(R.string.controls_media_empty_title, appName)
+            try {
+                statusBarManager.logBlankMediaTitle(sbn.packageName, sbn.user.identifier)
+            } catch (e: RuntimeException) {
+                Log.e(TAG, "Error reporting blank media title for package ${sbn.packageName}")
             }
         }
 
@@ -846,7 +832,7 @@
 
         // Artist name
         var artist: CharSequence? = metadata?.getString(MediaMetadata.METADATA_KEY_ARTIST)
-        if (artist == null) {
+        if (artist.isNullOrBlank()) {
             artist = HybridGroupManager.resolveText(notif)
         }
 
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 3751c60..9bc66f6 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
@@ -64,9 +64,4 @@
 
     /** Check whether we allow remote media to generate resume controls */
     fun isRemoteResumeAllowed() = featureFlags.isEnabled(Flags.MEDIA_REMOTE_RESUME)
-
-    /** Check whether app is required to provide a non-empty media title */
-    fun isMediaTitleRequired(packageName: String, user: UserHandle): Boolean {
-        return StatusBarManager.isMediaTitleRequiredForApp(packageName, user)
-    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
index bbc86c8..cfb581b 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
@@ -39,6 +39,7 @@
 import android.graphics.Rect;
 import android.graphics.Region;
 import android.hardware.input.InputManager;
+import android.icu.text.SimpleDateFormat;
 import android.os.Handler;
 import android.os.Looper;
 import android.os.RemoteException;
@@ -101,7 +102,9 @@
 import java.io.PrintWriter;
 import java.util.ArrayDeque;
 import java.util.ArrayList;
+import java.util.Date;
 import java.util.List;
+import java.util.Locale;
 import java.util.Map;
 import java.util.Optional;
 import java.util.concurrent.Executor;
@@ -287,6 +290,8 @@
     private LogArray mPredictionLog = new LogArray(MAX_NUM_LOGGED_PREDICTIONS);
     private LogArray mGestureLogInsideInsets = new LogArray(MAX_NUM_LOGGED_GESTURES);
     private LogArray mGestureLogOutsideInsets = new LogArray(MAX_NUM_LOGGED_GESTURES);
+    private SimpleDateFormat mLogDateFormat = new SimpleDateFormat("HH:mm:ss.SSS", Locale.US);
+    private Date mTmpLogDate = new Date();
 
     private final GestureNavigationSettingsObserver mGestureNavigationSettingsObserver;
 
@@ -1036,11 +1041,17 @@
             }
 
             // For debugging purposes, only log edge points
+            long curTime = System.currentTimeMillis();
+            mTmpLogDate.setTime(curTime);
+            String curTimeStr = mLogDateFormat.format(mTmpLogDate);
             (isWithinInsets ? mGestureLogInsideInsets : mGestureLogOutsideInsets).log(String.format(
-                    "Gesture [%d,alw=%B,%B,%B,%B,%B,%B,disp=%s,wl=%d,il=%d,wr=%d,ir=%d,excl=%s]",
-                    System.currentTimeMillis(), isTrackpadMultiFingerSwipe, mAllowGesture,
+                    "Gesture [%d [%s],alw=%B, mltf=%B, left=%B, defLeft=%B, backAlw=%B, disbld=%B,"
+                            + " qsDisbld=%b, blkdAct=%B, pip=%B,"
+                            + " disp=%s, wl=%d, il=%d, wr=%d, ir=%d, excl=%s]",
+                    curTime, curTimeStr, mAllowGesture, isTrackpadMultiFingerSwipe,
                     mIsOnLeftEdge, mDeferSetIsOnLeftEdge, mIsBackGestureAllowed,
-                    QuickStepContract.isBackGestureDisabled(mSysUiFlags), mDisplaySize,
+                    QuickStepContract.isBackGestureDisabled(mSysUiFlags), mDisabledForQuickstep,
+                    mGestureBlockingActivityRunning, mIsInPip, mDisplaySize,
                     mEdgeWidthLeft, mLeftInset, mEdgeWidthRight, mRightInset, mExcludeRegion));
         } else if (mAllowGesture || mLogGesture) {
             if (!mThresholdCrossed) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelper.java
index 993c3801..b956207 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelper.java
@@ -354,7 +354,11 @@
 
     @Override
     protected void snapChild(final View animView, final float targetLeft, float velocity) {
-        superSnapChild(animView, targetLeft, velocity);
+        if (animView instanceof SwipeableView) {
+            // only perform the snapback animation on views that are swipeable inside the shade.
+            superSnapChild(animView, targetLeft, velocity);
+        }
+
         mCallback.onDragCancelled(animView);
         if (targetLeft == 0) {
             handleMenuCoveredOrDismissed();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarController.java
index a6b2bd8..f26a84b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarController.java
@@ -30,6 +30,8 @@
 import android.view.ViewDebug;
 import android.view.WindowInsetsController.Appearance;
 
+import androidx.annotation.NonNull;
+
 import com.android.internal.colorextraction.ColorExtractor.GradientColors;
 import com.android.internal.view.AppearanceRegion;
 import com.android.systemui.Dumpable;
@@ -46,7 +48,6 @@
 
 import java.io.PrintWriter;
 import java.util.ArrayList;
-import java.util.Date;
 
 import javax.inject.Inject;
 
@@ -57,7 +58,8 @@
 public class LightBarController implements BatteryController.BatteryStateChangeCallback, Dumpable {
 
     private static final String TAG = "LightBarController";
-    private static final boolean DEBUG = Compile.IS_DEBUG && Log.isLoggable(TAG, Log.DEBUG);
+    private static final boolean DEBUG_NAVBAR = Compile.IS_DEBUG;
+    private static final boolean DEBUG_LOGS = Compile.IS_DEBUG && Log.isLoggable(TAG, Log.DEBUG);
 
     private static final float NAV_BAR_INVERSION_SCRIM_ALPHA_THRESHOLD = 0.1f;
 
@@ -113,6 +115,7 @@
 
     private String mLastSetScrimStateLog;
     private String mLastNavigationBarAppearanceChangedLog;
+    private StringBuilder mLogStringBuilder = null;
 
     @Inject
     public LightBarController(
@@ -193,35 +196,43 @@
                 final boolean darkForTop = darkForQs || mGlobalActionsVisible;
                 mNavigationLight =
                         ((mHasLightNavigationBar && !darkForScrim) || lightForScrim) && !darkForTop;
-                mLastNavigationBarAppearanceChangedLog = "onNavigationBarAppearanceChanged()"
-                        + " appearance=" + appearance
-                        + " nbModeChanged=" + nbModeChanged
-                        + " navigationBarMode=" + navigationBarMode
-                        + " navbarColorManagedByIme=" + navbarColorManagedByIme
-                        + " mHasLightNavigationBar=" + mHasLightNavigationBar
-                        + " ignoreScrimForce=" + ignoreScrimForce
-                        + " darkForScrim=" + darkForScrim
-                        + " lightForScrim=" + lightForScrim
-                        + " darkForQs=" + darkForQs
-                        + " darkForTop=" + darkForTop
-                        + " mNavigationLight=" + mNavigationLight
-                        + " last=" + last
-                        + " timestamp=" + new Date();
-                if (DEBUG) Log.d(TAG, mLastNavigationBarAppearanceChangedLog);
+                if (DEBUG_NAVBAR) {
+                    mLastNavigationBarAppearanceChangedLog = getLogStringBuilder()
+                            .append("onNavigationBarAppearanceChanged()")
+                            .append(" appearance=").append(appearance)
+                            .append(" nbModeChanged=").append(nbModeChanged)
+                            .append(" navigationBarMode=").append(navigationBarMode)
+                            .append(" navbarColorManagedByIme=").append(navbarColorManagedByIme)
+                            .append(" mHasLightNavigationBar=").append(mHasLightNavigationBar)
+                            .append(" ignoreScrimForce=").append(ignoreScrimForce)
+                            .append(" darkForScrim=").append(darkForScrim)
+                            .append(" lightForScrim=").append(lightForScrim)
+                            .append(" darkForQs=").append(darkForQs)
+                            .append(" darkForTop=").append(darkForTop)
+                            .append(" mNavigationLight=").append(mNavigationLight)
+                            .append(" last=").append(last)
+                            .append(" timestamp=").append(System.currentTimeMillis())
+                            .toString();
+                    if (DEBUG_LOGS) Log.d(TAG, mLastNavigationBarAppearanceChangedLog);
+                }
             } else {
                 mNavigationLight = mHasLightNavigationBar
                         && (mDirectReplying && mNavbarColorManagedByIme || !mForceDarkForScrim)
                         && !mQsCustomizing;
-                mLastNavigationBarAppearanceChangedLog = "onNavigationBarAppearanceChanged()"
-                        + " appearance=" + appearance
-                        + " nbModeChanged=" + nbModeChanged
-                        + " navigationBarMode=" + navigationBarMode
-                        + " navbarColorManagedByIme=" + navbarColorManagedByIme
-                        + " mHasLightNavigationBar=" + mHasLightNavigationBar
-                        + " mNavigationLight=" + mNavigationLight
-                        + " last=" + last
-                        + " timestamp=" + new Date();
-                if (DEBUG) Log.d(TAG, mLastNavigationBarAppearanceChangedLog);
+                if (DEBUG_NAVBAR) {
+                    mLastNavigationBarAppearanceChangedLog = getLogStringBuilder()
+                            .append("onNavigationBarAppearanceChanged()")
+                            .append(" appearance=").append(appearance)
+                            .append(" nbModeChanged=").append(nbModeChanged)
+                            .append(" navigationBarMode=").append(navigationBarMode)
+                            .append(" navbarColorManagedByIme=").append(navbarColorManagedByIme)
+                            .append(" mHasLightNavigationBar=").append(mHasLightNavigationBar)
+                            .append(" mNavigationLight=").append(mNavigationLight)
+                            .append(" last=").append(last)
+                            .append(" timestamp=").append(System.currentTimeMillis())
+                            .toString();
+                    if (DEBUG_LOGS) Log.d(TAG, mLastNavigationBarAppearanceChangedLog);
+                }
             }
             if (mNavigationLight != last) {
                 updateNavigation();
@@ -319,18 +330,22 @@
             } else {
                 if (mForceLightForScrim != forceLightForScrimLast) reevaluate();
             }
-            mLastSetScrimStateLog = "setScrimState()"
-                    + " scrimState=" + scrimState
-                    + " scrimBehindAlpha=" + scrimBehindAlpha
-                    + " scrimInFrontColor=" + scrimInFrontColor
-                    + " forceForScrim=" + forceForScrim
-                    + " scrimColorIsLight=" + scrimColorIsLight
-                    + " mHasLightNavigationBar=" + mHasLightNavigationBar
-                    + " mBouncerVisible=" + mBouncerVisible
-                    + " mForceDarkForScrim=" + mForceDarkForScrim
-                    + " mForceLightForScrim=" + mForceLightForScrim
-                    + " timestamp=" + new Date();
-            if (DEBUG) Log.d(TAG, mLastSetScrimStateLog);
+            if (DEBUG_NAVBAR) {
+                mLastSetScrimStateLog = getLogStringBuilder()
+                        .append("setScrimState()")
+                        .append(" scrimState=").append(scrimState)
+                        .append(" scrimBehindAlpha=").append(scrimBehindAlpha)
+                        .append(" scrimInFrontColor=").append(scrimInFrontColor)
+                        .append(" forceForScrim=").append(forceForScrim)
+                        .append(" scrimColorIsLight=").append(scrimColorIsLight)
+                        .append(" mHasLightNavigationBar=").append(mHasLightNavigationBar)
+                        .append(" mBouncerVisible=").append(mBouncerVisible)
+                        .append(" mForceDarkForScrim=").append(mForceDarkForScrim)
+                        .append(" mForceLightForScrim=").append(mForceLightForScrim)
+                        .append(" timestamp=").append(System.currentTimeMillis())
+                        .toString();
+                if (DEBUG_LOGS) Log.d(TAG, mLastSetScrimStateLog);
+            }
         } else {
             boolean forceDarkForScrimLast = mForceDarkForScrim;
             // For BOUNCER/BOUNCER_SCRIMMED cases, we assume that alpha is always below threshold.
@@ -344,17 +359,30 @@
             if (mHasLightNavigationBar && (mForceDarkForScrim != forceDarkForScrimLast)) {
                 reevaluate();
             }
-            mLastSetScrimStateLog = "setScrimState()"
-                    + " scrimState=" + scrimState
-                    + " scrimBehindAlpha=" + scrimBehindAlpha
-                    + " scrimInFrontColor=" + scrimInFrontColor
-                    + " mHasLightNavigationBar=" + mHasLightNavigationBar
-                    + " mForceDarkForScrim=" + mForceDarkForScrim
-                    + " timestamp=" + new Date();
-            if (DEBUG) Log.d(TAG, mLastSetScrimStateLog);
+            if (DEBUG_NAVBAR) {
+                mLastSetScrimStateLog = getLogStringBuilder()
+                        .append("setScrimState()")
+                        .append(" scrimState=").append(scrimState)
+                        .append(" scrimBehindAlpha=").append(scrimBehindAlpha)
+                        .append(" scrimInFrontColor=").append(scrimInFrontColor)
+                        .append(" mHasLightNavigationBar=").append(mHasLightNavigationBar)
+                        .append(" mForceDarkForScrim=").append(mForceDarkForScrim)
+                        .append(" timestamp=").append(System.currentTimeMillis())
+                        .toString();
+                if (DEBUG_LOGS) Log.d(TAG, mLastSetScrimStateLog);
+            }
         }
     }
 
+    @NonNull
+    private StringBuilder getLogStringBuilder() {
+        if (mLogStringBuilder == null) {
+            mLogStringBuilder = new StringBuilder();
+        }
+        mLogStringBuilder.setLength(0);
+        return mLogStringBuilder;
+    }
+
     private static boolean isLight(int appearance, int barMode, int flag) {
         final boolean isTransparentBar = (barMode == MODE_TRANSPARENT
                 || barMode == MODE_LIGHTS_OUT_TRANSPARENT);
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
index 5ba02fa..b24a692 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
@@ -73,7 +73,6 @@
 import android.os.SystemClock;
 import android.os.Trace;
 import android.os.VibrationEffect;
-import android.provider.DeviceConfig;
 import android.provider.Settings;
 import android.provider.Settings.Global;
 import android.text.InputFilter;
@@ -113,7 +112,6 @@
 import com.android.app.animation.Interpolators;
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.config.sysui.SystemUiDeviceConfigFlags;
 import com.android.internal.graphics.drawable.BackgroundBlurDrawable;
 import com.android.internal.jank.InteractionJankMonitor;
 import com.android.internal.view.RotationPolicy;
@@ -133,15 +131,11 @@
 import com.android.systemui.statusbar.policy.DevicePostureController;
 import com.android.systemui.statusbar.policy.DeviceProvisionedController;
 import com.android.systemui.util.AlphaTintDrawableWrapper;
-import com.android.systemui.util.DeviceConfigProxy;
 import com.android.systemui.util.RoundedCornerProgressDrawable;
 
 import java.io.PrintWriter;
 import java.util.ArrayList;
 import java.util.List;
-import java.util.Optional;
-import java.util.Set;
-import java.util.concurrent.Executor;
 import java.util.function.Consumer;
 
 /**
@@ -198,9 +192,6 @@
     private ViewGroup mDialogRowsView;
     private ViewGroup mRinger;
 
-    private DeviceConfigProxy mDeviceConfigProxy;
-    private Executor mExecutor;
-
     /**
      * Container for the top part of the dialog, which contains the ringer, the ringer drawer, the
      * volume rows, and the ellipsis button. This does not include the live caption button.
@@ -290,14 +281,12 @@
     private BackgroundBlurDrawable mDialogRowsViewBackground;
     private final InteractionJankMonitor mInteractionJankMonitor;
 
-    private boolean mSeparateNotification;
-
     private int mWindowGravity;
 
     @VisibleForTesting
-    int mVolumeRingerIconDrawableId;
+    final int mVolumeRingerIconDrawableId = R.drawable.ic_speaker_on;
     @VisibleForTesting
-    int mVolumeRingerMuteIconDrawableId;
+    final int mVolumeRingerMuteIconDrawableId = R.drawable.ic_speaker_mute;
 
     private int mOriginalGravity;
     private final DevicePostureController.Callback mDevicePostureControllerCallback;
@@ -315,8 +304,6 @@
             VolumePanelFactory volumePanelFactory,
             ActivityStarter activityStarter,
             InteractionJankMonitor interactionJankMonitor,
-            DeviceConfigProxy deviceConfigProxy,
-            Executor executor,
             CsdWarningDialog.Factory csdWarningDialogFactory,
             DevicePostureController devicePostureController,
             Looper looper,
@@ -374,12 +361,6 @@
         } else {
             mDevicePostureControllerCallback = null;
         }
-
-        mDeviceConfigProxy = deviceConfigProxy;
-        mExecutor = executor;
-        mSeparateNotification = mDeviceConfigProxy.getBoolean(DeviceConfig.NAMESPACE_SYSTEMUI,
-                SystemUiDeviceConfigFlags.VOLUME_SEPARATE_NOTIFICATION, false);
-        updateRingerModeIconSet();
     }
 
     /**
@@ -401,44 +382,6 @@
         return mWindowGravity;
     }
 
-    /**
-     * If ringer and notification are the same stream (T and earlier), use notification-like bell
-     * icon set.
-     * If ringer and notification are separated, then use generic speaker icons.
-     */
-    private void updateRingerModeIconSet() {
-        if (mSeparateNotification) {
-            mVolumeRingerIconDrawableId = R.drawable.ic_speaker_on;
-            mVolumeRingerMuteIconDrawableId = R.drawable.ic_speaker_mute;
-        } else {
-            mVolumeRingerIconDrawableId = R.drawable.ic_volume_ringer;
-            mVolumeRingerMuteIconDrawableId = R.drawable.ic_volume_ringer_mute;
-        }
-
-        if (mRingerDrawerMuteIcon != null) {
-            mRingerDrawerMuteIcon.setImageResource(mVolumeRingerMuteIconDrawableId);
-        }
-        if (mRingerDrawerNormalIcon != null) {
-            mRingerDrawerNormalIcon.setImageResource(mVolumeRingerIconDrawableId);
-        }
-    }
-
-    /**
-     * Change icon for ring stream (not ringer mode icon)
-     */
-    private void updateRingRowIcon() {
-        Optional<VolumeRow> volumeRow = mRows.stream().filter(row -> row.stream == STREAM_RING)
-                .findFirst();
-        if (volumeRow.isPresent()) {
-            VolumeRow volRow = volumeRow.get();
-            volRow.iconRes = mSeparateNotification ? R.drawable.ic_ring_volume
-                    : R.drawable.ic_volume_ringer;
-            volRow.iconMuteRes = mSeparateNotification ? R.drawable.ic_ring_volume_off
-                    : R.drawable.ic_volume_ringer_mute;
-            volRow.setIcon(volRow.iconRes, mContext.getTheme());
-        }
-    }
-
     @Override
     public void onUiModeChanged() {
         mContext.getTheme().applyStyle(mContext.getThemeResId(), true);
@@ -454,9 +397,6 @@
 
         mConfigurationController.addCallback(this);
 
-        mDeviceConfigProxy.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_SYSTEMUI,
-                mExecutor, this::onDeviceConfigChange);
-
         if (mDevicePostureController != null) {
             mDevicePostureController.addCallback(mDevicePostureControllerCallback);
         }
@@ -464,31 +404,15 @@
 
     @Override
     public void destroy() {
+        Log.d(TAG, "destroy() called");
         mController.removeCallback(mControllerCallbackH);
         mHandler.removeCallbacksAndMessages(null);
         mConfigurationController.removeCallback(this);
-        mDeviceConfigProxy.removeOnPropertiesChangedListener(this::onDeviceConfigChange);
         if (mDevicePostureController != null) {
             mDevicePostureController.removeCallback(mDevicePostureControllerCallback);
         }
     }
 
-    /**
-     * Update ringer mode icon based on the config
-     */
-    private void onDeviceConfigChange(DeviceConfig.Properties properties) {
-        Set<String> changeSet = properties.getKeyset();
-        if (changeSet.contains(SystemUiDeviceConfigFlags.VOLUME_SEPARATE_NOTIFICATION)) {
-            boolean newVal = properties.getBoolean(
-                    SystemUiDeviceConfigFlags.VOLUME_SEPARATE_NOTIFICATION, false);
-            if (newVal != mSeparateNotification) {
-                mSeparateNotification = newVal;
-                updateRingerModeIconSet();
-                updateRingRowIcon();
-            }
-        }
-    }
-
     @Override
     public void onComputeInternalInsets(ViewTreeObserver.InternalInsetsInfo internalInsetsInfo) {
         // Set touchable region insets on the root dialog view. This tells WindowManager that
@@ -542,6 +466,7 @@
     }
 
     private void initDialog(int lockTaskModeState) {
+        Log.d(TAG, "initDialog: called!");
         mDialog = new CustomDialog(mContext);
         initDimens();
 
@@ -699,7 +624,12 @@
         mRingerDrawerNormalIcon = mDialog.findViewById(R.id.volume_drawer_normal_icon);
         mRingerDrawerNewSelectionBg = mDialog.findViewById(R.id.volume_drawer_selection_background);
 
-        updateRingerModeIconSet();
+        if (mRingerDrawerMuteIcon != null) {
+            mRingerDrawerMuteIcon.setImageResource(mVolumeRingerMuteIconDrawableId);
+        }
+        if (mRingerDrawerNormalIcon != null) {
+            mRingerDrawerNormalIcon.setImageResource(mVolumeRingerIconDrawableId);
+        }
 
         setupRingerDrawer();
 
@@ -724,13 +654,10 @@
             addRow(AudioManager.STREAM_MUSIC,
                     R.drawable.ic_volume_media, R.drawable.ic_volume_media_mute, true, true);
             if (!AudioSystem.isSingleVolume(mContext)) {
-                if (mSeparateNotification) {
-                    addRow(AudioManager.STREAM_RING, R.drawable.ic_ring_volume,
-                            R.drawable.ic_ring_volume_off, true, false);
-                } else {
-                    addRow(AudioManager.STREAM_RING, R.drawable.ic_volume_ringer,
-                            R.drawable.ic_volume_ringer, true, false);
-                }
+
+                addRow(AudioManager.STREAM_RING, R.drawable.ic_ring_volume,
+                        R.drawable.ic_ring_volume_off, true, false);
+
 
                 addRow(STREAM_ALARM,
                         R.drawable.ic_alarm, R.drawable.ic_volume_alarm_mute, true, false);
@@ -1344,7 +1271,7 @@
     }
 
     protected void tryToRemoveCaptionsTooltip() {
-        if (mHasSeenODICaptionsTooltip && mODICaptionsTooltipView != null) {
+        if (mHasSeenODICaptionsTooltip && mODICaptionsTooltipView != null && mDialog != null) {
             ViewGroup container = mDialog.findViewById(R.id.volume_dialog_container);
             container.removeView(mODICaptionsTooltipView);
             mODICaptionsTooltipView = null;
@@ -1551,8 +1478,16 @@
 
         mHandler.removeMessages(H.DISMISS);
         mHandler.removeMessages(H.SHOW);
-        if (mIsAnimatingDismiss) {
-            Log.d(TAG, "dismissH: isAnimatingDismiss");
+
+        boolean showingStateInconsistent = !mShowing && mDialog != null && mDialog.isShowing();
+        // If incorrectly assuming dialog is not showing, continue and make the state consistent.
+        if (showingStateInconsistent) {
+            Log.d(TAG, "dismissH: volume dialog possible in inconsistent state:"
+                    + "mShowing=" + mShowing + ", mDialog==null?" + (mDialog == null));
+        }
+        if (mIsAnimatingDismiss && !showingStateInconsistent) {
+            Log.d(TAG, "dismissH: skipping dismiss because isAnimatingDismiss is true"
+                    + " and showingStateInconsistent is false");
             Trace.endSection();
             return;
         }
@@ -1570,8 +1505,12 @@
                 .setDuration(mDialogHideAnimationDurationMs)
                 .setInterpolator(new SystemUIInterpolators.LogAccelerateInterpolator())
                 .withEndAction(() -> mHandler.postDelayed(() -> {
-                    mController.notifyVisible(false);
-                    mDialog.dismiss();
+                    if (mController != null) {
+                        mController.notifyVisible(false);
+                    }
+                    if (mDialog != null) {
+                        mDialog.dismiss();
+                    }
                     tryToRemoveCaptionsTooltip();
                     mIsAnimatingDismiss = false;
 
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dagger/VolumeModule.java b/packages/SystemUI/src/com/android/systemui/volume/dagger/VolumeModule.java
index bb04f82..aa4ee54 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dagger/VolumeModule.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/dagger/VolumeModule.java
@@ -21,7 +21,6 @@
 import android.os.Looper;
 
 import com.android.internal.jank.InteractionJankMonitor;
-import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.media.dialog.MediaOutputDialogFactory;
 import com.android.systemui.plugins.ActivityStarter;
@@ -31,7 +30,6 @@
 import com.android.systemui.statusbar.policy.ConfigurationController;
 import com.android.systemui.statusbar.policy.DevicePostureController;
 import com.android.systemui.statusbar.policy.DeviceProvisionedController;
-import com.android.systemui.util.DeviceConfigProxy;
 import com.android.systemui.volume.CsdWarningDialog;
 import com.android.systemui.volume.VolumeComponent;
 import com.android.systemui.volume.VolumeDialogComponent;
@@ -42,8 +40,6 @@
 import dagger.Module;
 import dagger.Provides;
 
-import java.util.concurrent.Executor;
-
 /** Dagger Module for code in the volume package. */
 @Module
 public interface VolumeModule {
@@ -63,8 +59,6 @@
             VolumePanelFactory volumePanelFactory,
             ActivityStarter activityStarter,
             InteractionJankMonitor interactionJankMonitor,
-            DeviceConfigProxy deviceConfigProxy,
-            @Main Executor executor,
             CsdWarningDialog.Factory csdFactory,
             DevicePostureController devicePostureController,
             DumpManager dumpManager) {
@@ -78,8 +72,6 @@
                 volumePanelFactory,
                 activityStarter,
                 interactionJankMonitor,
-                deviceConfigProxy,
-                executor,
                 csdFactory,
                 devicePostureController,
                 Looper.getMainLooper(),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationSettingsTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationSettingsTest.java
index ce96708..df08dec 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationSettingsTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationSettingsTest.java
@@ -37,6 +37,7 @@
 import android.content.Context;
 import android.content.pm.ActivityInfo;
 import android.database.ContentObserver;
+import android.graphics.Rect;
 import android.os.UserHandle;
 import android.provider.Settings;
 import android.testing.AndroidTestingRunner;
@@ -49,6 +50,7 @@
 import android.widget.CompoundButton;
 import android.widget.LinearLayout;
 
+import androidx.test.InstrumentationRegistry;
 import androidx.test.filters.SmallTest;
 
 import com.android.internal.graphics.SfVsyncFrameCallbackProvider;
@@ -65,7 +67,7 @@
 
 @SmallTest
 @RunWith(AndroidTestingRunner.class)
-@TestableLooper.RunWithLooper(setAsMainLooper = true)
+@TestableLooper.RunWithLooper
 public class WindowMagnificationSettingsTest extends SysuiTestCase {
 
     private static final int MAGNIFICATION_SIZE_SMALL = 1;
@@ -275,6 +277,39 @@
     }
 
     @Test
+    public void onScreenSizeChanged_resetPositionToRightBottomCorner() {
+        setupMagnificationCapabilityAndMode(
+                /* capability= */ ACCESSIBILITY_MAGNIFICATION_MODE_ALL,
+                /* mode= */ ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW);
+        mWindowMagnificationSettings.showSettingPanel();
+
+        // move the panel to the center of draggable window bounds
+        mWindowMagnificationSettings.mParams.x =
+                mWindowMagnificationSettings.mDraggableWindowBounds.centerX();
+        mWindowMagnificationSettings.mParams.y =
+                mWindowMagnificationSettings.mDraggableWindowBounds.centerY();
+        mWindowMagnificationSettings.updateButtonViewLayoutIfNeeded();
+
+        final Rect testWindowBounds = new Rect(
+                mWindowManager.getCurrentWindowMetrics().getBounds());
+        testWindowBounds.set(testWindowBounds.left, testWindowBounds.top,
+                testWindowBounds.right + 200, testWindowBounds.bottom + 50);
+        mWindowManager.setWindowBounds(testWindowBounds);
+
+        InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
+            mWindowMagnificationSettings.onConfigurationChanged(ActivityInfo.CONFIG_SCREEN_SIZE);
+        });
+
+        // the panel position should be reset to the bottom-right corner
+        assertEquals(
+                mWindowMagnificationSettings.mParams.x,
+                mWindowMagnificationSettings.mDraggableWindowBounds.right);
+        assertEquals(
+                mWindowMagnificationSettings.mParams.y,
+                mWindowMagnificationSettings.mDraggableWindowBounds.bottom);
+    }
+
+    @Test
     public void showSettingsPanel_observerRegistered() {
         setupMagnificationCapabilityAndMode(
                 /* capability= */ ACCESSIBILITY_MAGNIFICATION_MODE_ALL,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractorTest.kt
index 44c9905..1990c8f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractorTest.kt
@@ -145,50 +145,59 @@
     @Test
     fun authenticate_withCorrectPin_returnsTrueAndUnlocksDevice() =
         testScope.runTest {
+            val failedAttemptCount by collectLastValue(underTest.failedAuthenticationAttempts)
             val isUnlocked by collectLastValue(underTest.isUnlocked)
             underTest.setAuthenticationMethod(AuthenticationMethodModel.PIN(1234))
             assertThat(isUnlocked).isFalse()
 
             assertThat(underTest.authenticate(listOf(1, 2, 3, 4))).isTrue()
             assertThat(isUnlocked).isTrue()
+            assertThat(failedAttemptCount).isEqualTo(0)
         }
 
     @Test
     fun authenticate_withIncorrectPin_returnsFalseAndDoesNotUnlockDevice() =
         testScope.runTest {
+            val failedAttemptCount by collectLastValue(underTest.failedAuthenticationAttempts)
             val isUnlocked by collectLastValue(underTest.isUnlocked)
             underTest.setAuthenticationMethod(AuthenticationMethodModel.PIN(1234))
             assertThat(isUnlocked).isFalse()
 
             assertThat(underTest.authenticate(listOf(9, 8, 7))).isFalse()
             assertThat(isUnlocked).isFalse()
+            assertThat(failedAttemptCount).isEqualTo(1)
         }
 
     @Test
     fun authenticate_withCorrectPassword_returnsTrueAndUnlocksDevice() =
         testScope.runTest {
+            val failedAttemptCount by collectLastValue(underTest.failedAuthenticationAttempts)
             val isUnlocked by collectLastValue(underTest.isUnlocked)
             underTest.setAuthenticationMethod(AuthenticationMethodModel.Password("password"))
             assertThat(isUnlocked).isFalse()
 
             assertThat(underTest.authenticate("password".toList())).isTrue()
             assertThat(isUnlocked).isTrue()
+            assertThat(failedAttemptCount).isEqualTo(0)
         }
 
     @Test
     fun authenticate_withIncorrectPassword_returnsFalseAndDoesNotUnlockDevice() =
         testScope.runTest {
+            val failedAttemptCount by collectLastValue(underTest.failedAuthenticationAttempts)
             val isUnlocked by collectLastValue(underTest.isUnlocked)
             underTest.setAuthenticationMethod(AuthenticationMethodModel.Password("password"))
             assertThat(isUnlocked).isFalse()
 
             assertThat(underTest.authenticate("alohomora".toList())).isFalse()
             assertThat(isUnlocked).isFalse()
+            assertThat(failedAttemptCount).isEqualTo(1)
         }
 
     @Test
     fun authenticate_withCorrectPattern_returnsTrueAndUnlocksDevice() =
         testScope.runTest {
+            val failedAttemptCount by collectLastValue(underTest.failedAuthenticationAttempts)
             val isUnlocked by collectLastValue(underTest.isUnlocked)
             underTest.setAuthenticationMethod(
                 AuthenticationMethodModel.Pattern(
@@ -230,11 +239,13 @@
                 )
                 .isTrue()
             assertThat(isUnlocked).isTrue()
+            assertThat(failedAttemptCount).isEqualTo(0)
         }
 
     @Test
     fun authenticate_withIncorrectPattern_returnsFalseAndDoesNotUnlockDevice() =
         testScope.runTest {
+            val failedAttemptCount by collectLastValue(underTest.failedAuthenticationAttempts)
             val isUnlocked by collectLastValue(underTest.isUnlocked)
             underTest.setAuthenticationMethod(
                 AuthenticationMethodModel.Pattern(
@@ -276,6 +287,7 @@
                 )
                 .isFalse()
             assertThat(isUnlocked).isFalse()
+            assertThat(failedAttemptCount).isEqualTo(1)
         }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractorTest.kt
index 730f89d..9f5c181 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractorTest.kt
@@ -27,6 +27,7 @@
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.advanceTimeBy
 import kotlinx.coroutines.test.runCurrent
 import kotlinx.coroutines.test.runTest
 import org.junit.Before
@@ -75,7 +76,7 @@
             assertThat(message).isEqualTo(MESSAGE_ENTER_YOUR_PIN)
 
             underTest.clearMessage()
-            assertThat(message).isNull()
+            assertThat(message).isEmpty()
 
             underTest.resetMessage()
             assertThat(message).isEqualTo(MESSAGE_ENTER_YOUR_PIN)
@@ -107,7 +108,7 @@
             assertThat(message).isEqualTo(MESSAGE_ENTER_YOUR_PASSWORD)
 
             underTest.clearMessage()
-            assertThat(message).isNull()
+            assertThat(message).isEmpty()
 
             underTest.resetMessage()
             assertThat(message).isEqualTo(MESSAGE_ENTER_YOUR_PASSWORD)
@@ -139,7 +140,7 @@
             assertThat(message).isEqualTo(MESSAGE_ENTER_YOUR_PATTERN)
 
             underTest.clearMessage()
-            assertThat(message).isNull()
+            assertThat(message).isEmpty()
 
             underTest.resetMessage()
             assertThat(message).isEqualTo(MESSAGE_ENTER_YOUR_PATTERN)
@@ -201,6 +202,56 @@
             assertThat(message).isEqualTo(customMessage)
         }
 
+    @Test
+    fun throttling() =
+        testScope.runTest {
+            val throttling by collectLastValue(underTest.throttling)
+            val message by collectLastValue(underTest.message)
+            val isUnlocked by collectLastValue(authenticationInteractor.isUnlocked)
+            authenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.PIN(1234))
+            assertThat(throttling).isNull()
+            assertThat(message).isEqualTo("")
+            assertThat(isUnlocked).isFalse()
+            repeat(BouncerInteractor.THROTTLE_EVERY) { times ->
+                // Wrong PIN.
+                underTest.authenticate(listOf(6, 7, 8, 9))
+                if (times < BouncerInteractor.THROTTLE_EVERY - 1) {
+                    assertThat(message).isEqualTo(MESSAGE_WRONG_PIN)
+                }
+            }
+            assertThat(throttling).isNotNull()
+            assertTryAgainMessage(message, BouncerInteractor.THROTTLE_DURATION_SEC)
+
+            // Correct PIN, but throttled, so doesn't unlock:
+            underTest.authenticate(listOf(1, 2, 3, 4))
+            assertThat(isUnlocked).isFalse()
+            assertTryAgainMessage(message, BouncerInteractor.THROTTLE_DURATION_SEC)
+
+            throttling?.totalDurationSec?.let { seconds ->
+                repeat(seconds) { time ->
+                    advanceTimeBy(1000)
+                    val remainingTime = seconds - time - 1
+                    if (remainingTime > 0) {
+                        assertTryAgainMessage(message, remainingTime)
+                    }
+                }
+            }
+            assertThat(message).isEqualTo("")
+            assertThat(throttling).isNull()
+            assertThat(isUnlocked).isFalse()
+
+            // Correct PIN and no longer throttled so unlocks:
+            underTest.authenticate(listOf(1, 2, 3, 4))
+            assertThat(isUnlocked).isTrue()
+        }
+
+    private fun assertTryAgainMessage(
+        message: String?,
+        time: Int,
+    ) {
+        assertThat(message).isEqualTo("Try again in $time seconds.")
+    }
+
     companion object {
         private const val MESSAGE_ENTER_YOUR_PIN = "Enter your PIN"
         private const val MESSAGE_ENTER_YOUR_PASSWORD = "Enter your password"
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelTest.kt
index 954e67d..b942ccb 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelTest.kt
@@ -19,11 +19,15 @@
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
+import com.android.systemui.bouncer.domain.interactor.BouncerInteractor
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.scene.SceneTestUtils
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.emptyFlow
+import kotlinx.coroutines.flow.flatMapLatest
 import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.advanceTimeBy
 import kotlinx.coroutines.test.runTest
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -40,13 +44,12 @@
         utils.authenticationInteractor(
             repository = utils.authenticationRepository(),
         )
-    private val underTest =
-        utils.bouncerViewModel(
-            utils.bouncerInteractor(
-                authenticationInteractor = authenticationInteractor,
-                sceneInteractor = utils.sceneInteractor(),
-            )
+    private val bouncerInteractor =
+        utils.bouncerInteractor(
+            authenticationInteractor = authenticationInteractor,
+            sceneInteractor = utils.sceneInteractor(),
         )
+    private val underTest = utils.bouncerViewModel(bouncerInteractor)
 
     @Test
     fun authMethod_nonNullForSecureMethods_nullForNotSecureMethods() =
@@ -89,6 +92,65 @@
             .isEqualTo(AuthenticationMethodModel::class.sealedSubclasses.toSet())
     }
 
+    @Test
+    fun isMessageUpdateAnimationsEnabled() =
+        testScope.runTest {
+            val isMessageUpdateAnimationsEnabled by
+                collectLastValue(underTest.isMessageUpdateAnimationsEnabled)
+            val throttling by collectLastValue(bouncerInteractor.throttling)
+            authenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.PIN(1234))
+            assertThat(isMessageUpdateAnimationsEnabled).isTrue()
+
+            repeat(BouncerInteractor.THROTTLE_EVERY) {
+                // Wrong PIN.
+                bouncerInteractor.authenticate(listOf(3, 4, 5, 6))
+            }
+            assertThat(isMessageUpdateAnimationsEnabled).isFalse()
+
+            throttling?.totalDurationSec?.let { seconds -> advanceTimeBy(seconds * 1000L) }
+            assertThat(isMessageUpdateAnimationsEnabled).isTrue()
+        }
+
+    @Test
+    fun isInputEnabled() =
+        testScope.runTest {
+            val isInputEnabled by
+                collectLastValue(
+                    underTest.authMethod.flatMapLatest { authViewModel ->
+                        authViewModel?.isInputEnabled ?: emptyFlow()
+                    }
+                )
+            val throttling by collectLastValue(bouncerInteractor.throttling)
+            authenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.PIN(1234))
+            assertThat(isInputEnabled).isTrue()
+
+            repeat(BouncerInteractor.THROTTLE_EVERY) {
+                // Wrong PIN.
+                bouncerInteractor.authenticate(listOf(3, 4, 5, 6))
+            }
+            assertThat(isInputEnabled).isFalse()
+
+            throttling?.totalDurationSec?.let { seconds -> advanceTimeBy(seconds * 1000L) }
+            assertThat(isInputEnabled).isTrue()
+        }
+
+    @Test
+    fun throttlingDialogMessage() =
+        testScope.runTest {
+            val throttlingDialogMessage by collectLastValue(underTest.throttlingDialogMessage)
+            authenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.PIN(1234))
+
+            repeat(BouncerInteractor.THROTTLE_EVERY) {
+                // Wrong PIN.
+                assertThat(throttlingDialogMessage).isNull()
+                bouncerInteractor.authenticate(listOf(3, 4, 5, 6))
+            }
+            assertThat(throttlingDialogMessage).isNotEmpty()
+
+            underTest.onThrottlingDialogDismissed()
+            assertThat(throttlingDialogMessage).isNull()
+        }
+
     private fun authMethodsToTest(): List<AuthenticationMethodModel> {
         return listOf(
             AuthenticationMethodModel.None,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModelTest.kt
index e48b638..b7b90de 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModelTest.kt
@@ -26,6 +26,8 @@
 import com.android.systemui.scene.shared.model.SceneModel
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.asStateFlow
 import kotlinx.coroutines.test.TestScope
 import kotlinx.coroutines.test.runTest
 import org.junit.Before
@@ -57,6 +59,7 @@
     private val underTest =
         PasswordBouncerViewModel(
             interactor = bouncerInteractor,
+            isInputEnabled = MutableStateFlow(true).asStateFlow(),
         )
 
     @Before
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModelTest.kt
index 6ce29e6..b588ba2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModelTest.kt
@@ -27,6 +27,8 @@
 import com.google.common.truth.Truth.assertThat
 import com.google.common.truth.Truth.assertWithMessage
 import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.asStateFlow
 import kotlinx.coroutines.test.TestScope
 import kotlinx.coroutines.test.runTest
 import org.junit.Before
@@ -60,6 +62,7 @@
             applicationContext = context,
             applicationScope = testScope.backgroundScope,
             interactor = bouncerInteractor,
+            isInputEnabled = MutableStateFlow(true).asStateFlow(),
         )
 
     @Before
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 bb28520..83f9687 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
@@ -27,6 +27,8 @@
 import com.android.systemui.scene.shared.model.SceneModel
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.asStateFlow
 import kotlinx.coroutines.test.TestScope
 import kotlinx.coroutines.test.advanceTimeBy
 import kotlinx.coroutines.test.runTest
@@ -68,6 +70,7 @@
         PinBouncerViewModel(
             applicationScope = testScope.backgroundScope,
             interactor = bouncerInteractor,
+            isInputEnabled = MutableStateFlow(true).asStateFlow(),
         )
 
     @Before
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ResourceTrimmerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ResourceTrimmerTest.kt
index 548d26f..78a65a8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ResourceTrimmerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ResourceTrimmerTest.kt
@@ -98,7 +98,8 @@
             )
             testScope.runCurrent()
             verify(globalWindowManager, times(1))
-                .trimMemory(ComponentCallbacks2.TRIM_MEMORY_BACKGROUND)
+                .trimMemory(ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN)
+            verify(globalWindowManager, times(1)).trimCaches(HardwareRenderer.CACHE_TRIM_ALL)
         }
 
     @Test
@@ -115,7 +116,8 @@
             )
             testScope.runCurrent()
             verify(globalWindowManager, times(1))
-                .trimMemory(ComponentCallbacks2.TRIM_MEMORY_BACKGROUND)
+                .trimMemory(ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN)
+            verify(globalWindowManager, times(1)).trimCaches(HardwareRenderer.CACHE_TRIM_ALL)
         }
 
     @Test
@@ -161,7 +163,8 @@
             keyguardRepository.setDozeAmount(1f)
             testScope.runCurrent()
             verify(globalWindowManager, times(1))
-                .trimMemory(ComponentCallbacks2.TRIM_MEMORY_BACKGROUND)
+                .trimMemory(ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN)
+            verify(globalWindowManager, times(1)).trimCaches(HardwareRenderer.CACHE_TRIM_ALL)
         }
     }
 
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 fd6e457..56698e0 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
@@ -41,7 +41,6 @@
 import androidx.media.utils.MediaConstants
 import androidx.test.filters.SmallTest
 import com.android.internal.logging.InstanceId
-import com.android.internal.statusbar.IStatusBarService
 import com.android.keyguard.KeyguardUpdateMonitor
 import com.android.systemui.InstanceIdSequenceFake
 import com.android.systemui.R
@@ -133,7 +132,6 @@
     @Mock lateinit var activityStarter: ActivityStarter
     @Mock lateinit var smartspaceManager: SmartspaceManager
     @Mock lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
-    @Mock lateinit var statusBarService: IStatusBarService
     lateinit var smartspaceMediaDataProvider: SmartspaceMediaDataProvider
     @Mock lateinit var mediaSmartspaceTarget: SmartspaceTarget
     @Mock private lateinit var mediaRecommendationItem: SmartspaceAction
@@ -197,7 +195,6 @@
                 logger = logger,
                 smartspaceManager = smartspaceManager,
                 keyguardUpdateMonitor = keyguardUpdateMonitor,
-                statusBarService = statusBarService,
             )
         verify(tunerService)
             .addTunable(capture(tunableCaptor), eq(Settings.Secure.MEDIA_CONTROLS_RECOMMENDATION))
@@ -522,143 +519,12 @@
     }
 
     @Test
-    fun testOnNotificationAdded_emptyTitle_isRequired_notLoaded() {
-        // When the manager has a notification with an empty title, and the app is required
-        // to include a non-empty title
-        whenever(mediaFlags.isMediaTitleRequired(any(), any())).thenReturn(true)
-        whenever(controller.metadata)
-            .thenReturn(
-                metadataBuilder
-                    .putString(MediaMetadata.METADATA_KEY_TITLE, SESSION_EMPTY_TITLE)
-                    .build()
-            )
-        mediaDataManager.onNotificationAdded(KEY, mediaNotification)
-
-        // Then the media control is not added and we report a notification error
-        assertThat(backgroundExecutor.runAllReady()).isEqualTo(1)
-        assertThat(foregroundExecutor.runAllReady()).isEqualTo(1)
-        verify(statusBarService)
-            .onNotificationError(
-                eq(PACKAGE_NAME),
-                eq(mediaNotification.tag),
-                eq(mediaNotification.id),
-                eq(mediaNotification.uid),
-                eq(mediaNotification.initialPid),
-                eq(MEDIA_TITLE_ERROR_MESSAGE),
-                eq(mediaNotification.user.identifier)
-            )
-        verify(listener, never())
-            .onMediaDataLoaded(
-                eq(KEY),
-                eq(null),
-                capture(mediaDataCaptor),
-                eq(true),
-                eq(0),
-                eq(false)
-            )
-        verify(logger, never()).logResumeMediaAdded(anyInt(), eq(PACKAGE_NAME), any())
-        verify(logger, never()).logMediaRemoved(anyInt(), eq(PACKAGE_NAME), any())
-    }
-
-    @Test
-    fun testOnNotificationAdded_blankTitle_isRequired_notLoaded() {
-        // When the manager has a notification with a blank title, and the app is required
-        // to include a non-empty title
-        whenever(mediaFlags.isMediaTitleRequired(any(), any())).thenReturn(true)
-        whenever(controller.metadata)
-            .thenReturn(
-                metadataBuilder
-                    .putString(MediaMetadata.METADATA_KEY_TITLE, SESSION_BLANK_TITLE)
-                    .build()
-            )
-        mediaDataManager.onNotificationAdded(KEY, mediaNotification)
-
-        // Then the media control is not added and we report a notification error
-        assertThat(backgroundExecutor.runAllReady()).isEqualTo(1)
-        assertThat(foregroundExecutor.runAllReady()).isEqualTo(1)
-        verify(statusBarService)
-            .onNotificationError(
-                eq(PACKAGE_NAME),
-                eq(mediaNotification.tag),
-                eq(mediaNotification.id),
-                eq(mediaNotification.uid),
-                eq(mediaNotification.initialPid),
-                eq(MEDIA_TITLE_ERROR_MESSAGE),
-                eq(mediaNotification.user.identifier)
-            )
-        verify(listener, never())
-            .onMediaDataLoaded(
-                eq(KEY),
-                eq(null),
-                capture(mediaDataCaptor),
-                eq(true),
-                eq(0),
-                eq(false)
-            )
-        verify(logger, never()).logResumeMediaAdded(anyInt(), eq(PACKAGE_NAME), any())
-        verify(logger, never()).logMediaRemoved(anyInt(), eq(PACKAGE_NAME), any())
-    }
-
-    @Test
-    fun testOnNotificationUpdated_invalidTitle_isRequired_logMediaRemoved() {
-        // When the app is required to provide a non-blank title, and updates a previously valid
-        // title to an empty one
-        whenever(mediaFlags.isMediaTitleRequired(any(), any())).thenReturn(true)
-        addNotificationAndLoad()
-        val data = mediaDataCaptor.value
-
-        verify(listener)
-            .onMediaDataLoaded(
-                eq(KEY),
-                eq(null),
-                capture(mediaDataCaptor),
-                eq(true),
-                eq(0),
-                eq(false)
-            )
-
-        reset(listener)
-        whenever(controller.metadata)
-            .thenReturn(
-                metadataBuilder
-                    .putString(MediaMetadata.METADATA_KEY_TITLE, SESSION_BLANK_TITLE)
-                    .build()
-            )
-        mediaDataManager.onNotificationAdded(KEY, mediaNotification)
-
-        // Then the media control is removed
-        assertThat(backgroundExecutor.runAllReady()).isEqualTo(1)
-        assertThat(foregroundExecutor.runAllReady()).isEqualTo(1)
-        verify(statusBarService)
-            .onNotificationError(
-                eq(PACKAGE_NAME),
-                eq(mediaNotification.tag),
-                eq(mediaNotification.id),
-                eq(mediaNotification.uid),
-                eq(mediaNotification.initialPid),
-                eq(MEDIA_TITLE_ERROR_MESSAGE),
-                eq(mediaNotification.user.identifier)
-            )
-        verify(listener, never())
-            .onMediaDataLoaded(
-                eq(KEY),
-                eq(null),
-                capture(mediaDataCaptor),
-                eq(true),
-                eq(0),
-                eq(false)
-            )
-        verify(logger).logMediaRemoved(anyInt(), eq(PACKAGE_NAME), eq(data.instanceId))
-    }
-
-    @Test
-    fun testOnNotificationAdded_emptyTitle_notRequired_hasPlaceholder() {
+    fun testOnNotificationAdded_emptyTitle_hasPlaceholder() {
         // When the manager has a notification with an empty title, and the app is not
         // required to include a non-empty title
         val mockPackageManager = mock(PackageManager::class.java)
         context.setMockPackageManager(mockPackageManager)
         whenever(mockPackageManager.getApplicationLabel(any())).thenReturn(APP_NAME)
-        whenever(mediaFlags.isMediaTitleRequired(any(), any())).thenReturn(false)
         whenever(controller.metadata)
             .thenReturn(
                 metadataBuilder
@@ -684,13 +550,12 @@
     }
 
     @Test
-    fun testOnNotificationAdded_blankTitle_notRequired_hasPlaceholder() {
+    fun testOnNotificationAdded_blankTitle_hasPlaceholder() {
         // GIVEN that the manager has a notification with a blank title, and the app is not
         // required to include a non-empty title
         val mockPackageManager = mock(PackageManager::class.java)
         context.setMockPackageManager(mockPackageManager)
         whenever(mockPackageManager.getApplicationLabel(any())).thenReturn(APP_NAME)
-        whenever(mediaFlags.isMediaTitleRequired(any(), any())).thenReturn(false)
         whenever(controller.metadata)
             .thenReturn(
                 metadataBuilder
@@ -716,6 +581,47 @@
     }
 
     @Test
+    fun testOnNotificationAdded_emptyMetadata_usesNotificationTitle() {
+        // When the app sets the metadata title fields to empty strings, but does include a
+        // non-blank notification title
+        val mockPackageManager = mock(PackageManager::class.java)
+        context.setMockPackageManager(mockPackageManager)
+        whenever(mockPackageManager.getApplicationLabel(any())).thenReturn(APP_NAME)
+        whenever(controller.metadata)
+            .thenReturn(
+                metadataBuilder
+                    .putString(MediaMetadata.METADATA_KEY_TITLE, SESSION_EMPTY_TITLE)
+                    .putString(MediaMetadata.METADATA_KEY_DISPLAY_TITLE, SESSION_EMPTY_TITLE)
+                    .build()
+            )
+        mediaNotification =
+            SbnBuilder().run {
+                setPkg(PACKAGE_NAME)
+                modifyNotification(context).also {
+                    it.setSmallIcon(android.R.drawable.ic_media_pause)
+                    it.setContentTitle(SESSION_TITLE)
+                    it.setStyle(MediaStyle().apply { setMediaSession(session.sessionToken) })
+                }
+                build()
+            }
+        mediaDataManager.onNotificationAdded(KEY, mediaNotification)
+
+        // Then the media control is added using the notification's title
+        assertThat(backgroundExecutor.runAllReady()).isEqualTo(1)
+        assertThat(foregroundExecutor.runAllReady()).isEqualTo(1)
+        verify(listener)
+            .onMediaDataLoaded(
+                eq(KEY),
+                eq(null),
+                capture(mediaDataCaptor),
+                eq(true),
+                eq(0),
+                eq(false)
+            )
+        assertThat(mediaDataCaptor.value.song).isEqualTo(SESSION_TITLE)
+    }
+
+    @Test
     fun testOnNotificationRemoved_emptyTitle_notConverted() {
         // GIVEN that the manager has a notification with a resume action and empty title.
         addNotificationAndLoad()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/AnimatableClockViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/AnimatableClockViewTest.kt
index 2eca78a..e92368d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/AnimatableClockViewTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/AnimatableClockViewTest.kt
@@ -19,6 +19,7 @@
 import android.testing.AndroidTestingRunner
 import android.view.LayoutInflater
 import androidx.test.filters.SmallTest
+import com.android.app.animation.Interpolators
 import com.android.systemui.R
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.animation.TextAnimator
@@ -64,8 +65,8 @@
                 color = 200,
                 strokeWidth = -1F,
                 animate = false,
-                duration = 350L,
-                interpolator = null,
+                duration = 833L,
+                interpolator = Interpolators.EMPHASIZED_DECELERATE,
                 delay = 0L,
                 onAnimationEnd = null
             )
@@ -98,8 +99,8 @@
                 color = 200,
                 strokeWidth = -1F,
                 animate = true,
-                duration = 350L,
-                interpolator = null,
+                duration = 833L,
+                interpolator = Interpolators.EMPHASIZED_DECELERATE,
                 delay = 0L,
                 onAnimationEnd = null
             )
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelperTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelperTest.java
index 551499e..7632d01 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelperTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelperTest.java
@@ -392,23 +392,32 @@
 
     @Test
     public void testSnapchild_targetIsZero() {
-        doNothing().when(mSwipeHelper).superSnapChild(mView, 0, 0);
-        mSwipeHelper.snapChild(mView, 0, 0);
+        doNothing().when(mSwipeHelper).superSnapChild(mNotificationRow, 0, 0);
+        mSwipeHelper.snapChild(mNotificationRow, 0, 0);
 
-        verify(mCallback, times(1)).onDragCancelled(mView);
-        verify(mSwipeHelper, times(1)).superSnapChild(mView, 0, 0);
+        verify(mCallback, times(1)).onDragCancelled(mNotificationRow);
+        verify(mSwipeHelper, times(1)).superSnapChild(mNotificationRow, 0, 0);
         verify(mSwipeHelper, times(1)).handleMenuCoveredOrDismissed();
     }
 
 
     @Test
     public void testSnapchild_targetNotZero() {
+        doNothing().when(mSwipeHelper).superSnapChild(mNotificationRow, 10, 0);
+        mSwipeHelper.snapChild(mNotificationRow, 10, 0);
+
+        verify(mCallback, times(1)).onDragCancelled(mNotificationRow);
+        verify(mSwipeHelper, times(1)).superSnapChild(mNotificationRow, 10, 0);
+        verify(mSwipeHelper, times(0)).handleMenuCoveredOrDismissed();
+    }
+
+    @Test
+    public void testSnapchild_targetNotSwipeable() {
         doNothing().when(mSwipeHelper).superSnapChild(mView, 10, 0);
         mSwipeHelper.snapChild(mView, 10, 0);
 
-        verify(mCallback, times(1)).onDragCancelled(mView);
-        verify(mSwipeHelper, times(1)).superSnapChild(mView, 10, 0);
-        verify(mSwipeHelper, times(0)).handleMenuCoveredOrDismissed();
+        verify(mCallback).onDragCancelled(mView);
+        verify(mSwipeHelper, never()).superSnapChild(mView, 10, 0);
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java
index 45a37cf..8f725be 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java
@@ -35,7 +35,6 @@
 import android.content.res.Configuration;
 import android.media.AudioManager;
 import android.os.SystemClock;
-import android.provider.DeviceConfig;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
 import android.view.Gravity;
@@ -47,7 +46,6 @@
 
 import androidx.test.filters.SmallTest;
 
-import com.android.internal.config.sysui.SystemUiDeviceConfigFlags;
 import com.android.internal.jank.InteractionJankMonitor;
 import com.android.systemui.Prefs;
 import com.android.systemui.R;
@@ -62,9 +60,6 @@
 import com.android.systemui.statusbar.policy.DevicePostureController;
 import com.android.systemui.statusbar.policy.DeviceProvisionedController;
 import com.android.systemui.statusbar.policy.FakeConfigurationController;
-import com.android.systemui.util.DeviceConfigProxyFake;
-import com.android.systemui.util.concurrency.FakeExecutor;
-import com.android.systemui.util.time.FakeSystemClock;
 
 import org.junit.After;
 import org.junit.Before;
@@ -88,8 +83,6 @@
     View mDrawerVibrate;
     View mDrawerMute;
     View mDrawerNormal;
-    private DeviceConfigProxyFake mDeviceConfigProxy;
-    private FakeExecutor mExecutor;
     private TestableLooper mTestableLooper;
     private ConfigurationController mConfigurationController;
     private int mOriginalOrientation;
@@ -131,8 +124,6 @@
         getContext().addMockSystemService(KeyguardManager.class, mKeyguard);
 
         mTestableLooper = TestableLooper.get(this);
-        mDeviceConfigProxy = new DeviceConfigProxyFake();
-        mExecutor = new FakeExecutor(new FakeSystemClock());
 
         when(mPostureController.getDevicePosture())
                 .thenReturn(DevicePostureController.DEVICE_POSTURE_CLOSED);
@@ -151,8 +142,6 @@
                 mVolumePanelFactory,
                 mActivityStarter,
                 mInteractionJankMonitor,
-                mDeviceConfigProxy,
-                mExecutor,
                 mCsdWarningDialogFactory,
                 mPostureController,
                 mTestableLooper.getLooper(),
@@ -173,9 +162,6 @@
                 VolumePrefs.SHOW_RINGER_TOAST_COUNT + 1);
 
         Prefs.putBoolean(mContext, Prefs.Key.HAS_SEEN_ODI_CAPTIONS_TOOLTIP, false);
-
-        mDeviceConfigProxy.setProperty(DeviceConfig.NAMESPACE_SYSTEMUI,
-                SystemUiDeviceConfigFlags.VOLUME_SEPARATE_NOTIFICATION, "false", false);
     }
 
     private State createShellState() {
@@ -351,13 +337,8 @@
      * API does not exist. So we do the next best thing; we check the cached icon id.
      */
     @Test
-    public void notificationVolumeSeparated_theRingerIconChanges() {
-        mDeviceConfigProxy.setProperty(DeviceConfig.NAMESPACE_SYSTEMUI,
-                SystemUiDeviceConfigFlags.VOLUME_SEPARATE_NOTIFICATION, "true", false);
-
-        mExecutor.runAllReady(); // for the config change to take effect
-
-        // assert icon is new based on res id
+    public void notificationVolumeSeparated_theRingerIconChangesToSpeakerIcon() {
+        // already separated. assert icon is new based on res id
         assertEquals(mDialog.mVolumeRingerIconDrawableId,
                 R.drawable.ic_speaker_on);
         assertEquals(mDialog.mVolumeRingerMuteIconDrawableId,
@@ -365,17 +346,6 @@
     }
 
     @Test
-    public void notificationVolumeNotSeparated_theRingerIconRemainsTheSame() {
-        mDeviceConfigProxy.setProperty(DeviceConfig.NAMESPACE_SYSTEMUI,
-                SystemUiDeviceConfigFlags.VOLUME_SEPARATE_NOTIFICATION, "false", false);
-
-        mExecutor.runAllReady();
-
-        assertEquals(mDialog.mVolumeRingerIconDrawableId, R.drawable.ic_volume_ringer);
-        assertEquals(mDialog.mVolumeRingerMuteIconDrawableId, R.drawable.ic_volume_ringer_mute);
-    }
-
-    @Test
     public void testDialogDismissAnimation_notifyVisibleIsNotCalledBeforeAnimation() {
         mDialog.dismissH(DISMISS_REASON_UNKNOWN);
         // notifyVisible(false) should not be called immediately but only after the dismiss
@@ -408,8 +378,6 @@
                 mVolumePanelFactory,
                 mActivityStarter,
                 mInteractionJankMonitor,
-                mDeviceConfigProxy,
-                mExecutor,
                 mCsdWarningDialogFactory,
                 devicePostureController,
                 mTestableLooper.getLooper(),
@@ -447,8 +415,6 @@
                 mVolumePanelFactory,
                 mActivityStarter,
                 mInteractionJankMonitor,
-                mDeviceConfigProxy,
-                mExecutor,
                 mCsdWarningDialogFactory,
                 devicePostureController,
                 mTestableLooper.getLooper(),
@@ -485,8 +451,6 @@
                 mVolumePanelFactory,
                 mActivityStarter,
                 mInteractionJankMonitor,
-                mDeviceConfigProxy,
-                mExecutor,
                 mCsdWarningDialogFactory,
                 devicePostureController,
                 mTestableLooper.getLooper(),
@@ -525,8 +489,6 @@
                 mVolumePanelFactory,
                 mActivityStarter,
                 mInteractionJankMonitor,
-                mDeviceConfigProxy,
-                mExecutor,
                 mCsdWarningDialogFactory,
                 mPostureController,
                 mTestableLooper.getLooper(),
diff --git a/services/backup/java/com/android/server/backup/UserBackupManagerService.java b/services/backup/java/com/android/server/backup/UserBackupManagerService.java
index 995e557..d5aee92 100644
--- a/services/backup/java/com/android/server/backup/UserBackupManagerService.java
+++ b/services/backup/java/com/android/server/backup/UserBackupManagerService.java
@@ -2859,8 +2859,7 @@
             if (DEBUG) {
                 Slog.d(
                         TAG,
-                        addUserIdToLogMessage(
-                                mUserId, "Starting backup confirmation UI, token=" + token));
+                        addUserIdToLogMessage(mUserId, "Starting backup confirmation UI"));
             }
             if (!startConfirmationUi(token, FullBackup.FULL_BACKUP_INTENT_ACTION)) {
                 Slog.e(
diff --git a/services/contentcapture/java/com/android/server/contentprotection/ContentProtectionBlocklistManager.java b/services/contentcapture/java/com/android/server/contentprotection/ContentProtectionBlocklistManager.java
new file mode 100644
index 0000000..715cf9a
--- /dev/null
+++ b/services/contentcapture/java/com/android/server/contentprotection/ContentProtectionBlocklistManager.java
@@ -0,0 +1,111 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.contentprotection;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.pm.PackageInfo;
+import android.util.Slog;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.io.BufferedReader;
+import java.io.FileReader;
+import java.util.Collections;
+import java.util.List;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+/**
+ * Manages whether the content protection is enabled for an app using a blocklist.
+ *
+ * @hide
+ */
+class ContentProtectionBlocklistManager {
+
+    private static final String TAG = "ContentProtectionBlocklistManager";
+
+    private static final String PACKAGE_NAME_BLOCKLIST_FILENAME =
+            "/product/etc/res/raw/content_protection/package_name_blocklist.txt";
+
+    @NonNull private final ContentProtectionPackageManager mContentProtectionPackageManager;
+
+    @Nullable private Set<String> mPackageNameBlocklist;
+
+    protected ContentProtectionBlocklistManager(
+            @NonNull ContentProtectionPackageManager contentProtectionPackageManager) {
+        mContentProtectionPackageManager = contentProtectionPackageManager;
+    }
+
+    public boolean isAllowed(@NonNull String packageName) {
+        if (mPackageNameBlocklist == null) {
+            // List not loaded or failed to load, don't run on anything
+            return false;
+        }
+        if (mPackageNameBlocklist.contains(packageName)) {
+            return false;
+        }
+        PackageInfo packageInfo = mContentProtectionPackageManager.getPackageInfo(packageName);
+        if (packageInfo == null) {
+            return false;
+        }
+        if (!mContentProtectionPackageManager.hasRequestedInternetPermissions(packageInfo)) {
+            return false;
+        }
+        if (mContentProtectionPackageManager.isSystemApp(packageInfo)) {
+            return false;
+        }
+        if (mContentProtectionPackageManager.isUpdatedSystemApp(packageInfo)) {
+            return false;
+        }
+        return true;
+    }
+
+    public void updateBlocklist(int blocklistSize) {
+        Slog.i(TAG, "Blocklist size updating to: " + blocklistSize);
+        mPackageNameBlocklist = readPackageNameBlocklist(blocklistSize);
+    }
+
+    @Nullable
+    private Set<String> readPackageNameBlocklist(int blocklistSize) {
+        if (blocklistSize <= 0) {
+            // Explicitly requested an empty blocklist
+            return Collections.emptySet();
+        }
+        List<String> lines = readLinesFromRawFile(PACKAGE_NAME_BLOCKLIST_FILENAME);
+        if (lines == null) {
+            return null;
+        }
+        return lines.stream().limit(blocklistSize).collect(Collectors.toSet());
+    }
+
+    @VisibleForTesting
+    @Nullable
+    protected List<String> readLinesFromRawFile(@NonNull String filename) {
+        try (FileReader fileReader = new FileReader(filename);
+                BufferedReader bufferedReader = new BufferedReader(fileReader)) {
+            return bufferedReader
+                    .lines()
+                    .map(line -> line.trim())
+                    .filter(line -> !line.isBlank())
+                    .collect(Collectors.toList());
+        } catch (Exception ex) {
+            Slog.e(TAG, "Failed to read: " + filename, ex);
+            return null;
+        }
+    }
+}
diff --git a/services/contentcapture/java/com/android/server/contentprotection/ContentProtectionPackageManager.java b/services/contentcapture/java/com/android/server/contentprotection/ContentProtectionPackageManager.java
index 5221468..1847e5d 100644
--- a/services/contentcapture/java/com/android/server/contentprotection/ContentProtectionPackageManager.java
+++ b/services/contentcapture/java/com/android/server/contentprotection/ContentProtectionPackageManager.java
@@ -34,8 +34,9 @@
  *
  * @hide
  */
-final class ContentProtectionPackageManager {
-    private static final String TAG = ContentProtectionPackageManager.class.getSimpleName();
+public class ContentProtectionPackageManager {
+
+    private static final String TAG = "ContentProtectionPackageManager";
 
     private static final PackageInfoFlags PACKAGE_INFO_FLAGS =
             PackageInfoFlags.of(PackageManager.GET_PERMISSIONS);
@@ -51,7 +52,7 @@
         try {
             return mPackageManager.getPackageInfo(packageName, PACKAGE_INFO_FLAGS);
         } catch (NameNotFoundException ex) {
-            Slog.w(TAG, "Package info not found: ", ex);
+            Slog.w(TAG, "Package info not found for: " + packageName, ex);
             return null;
         }
     }
diff --git a/services/core/java/com/android/server/am/BroadcastConstants.java b/services/core/java/com/android/server/am/BroadcastConstants.java
index 030d596..8c1fd51 100644
--- a/services/core/java/com/android/server/am/BroadcastConstants.java
+++ b/services/core/java/com/android/server/am/BroadcastConstants.java
@@ -292,6 +292,15 @@
     private static final String KEY_CORE_DEFER_UNTIL_ACTIVE = "bcast_core_defer_until_active";
     private static final boolean DEFAULT_CORE_DEFER_UNTIL_ACTIVE = true;
 
+    /**
+     * For {@link BroadcastQueueModernImpl}: How frequently we should check for the pending
+     * cold start validity.
+     */
+    public long PENDING_COLD_START_CHECK_INTERVAL_MILLIS = 30 * 1000;
+    private static final String KEY_PENDING_COLD_START_CHECK_INTERVAL_MILLIS =
+            "pending_cold_start_check_interval_millis";
+    private static final long DEFAULT_PENDING_COLD_START_CHECK_INTERVAL_MILLIS = 30_000;
+
     // Settings override tracking for this instance
     private String mSettingsKey;
     private SettingsObserver mSettingsObserver;
@@ -441,6 +450,9 @@
                     DEFAULT_MAX_HISTORY_SUMMARY_SIZE);
             CORE_DEFER_UNTIL_ACTIVE = getDeviceConfigBoolean(KEY_CORE_DEFER_UNTIL_ACTIVE,
                     DEFAULT_CORE_DEFER_UNTIL_ACTIVE);
+            PENDING_COLD_START_CHECK_INTERVAL_MILLIS = getDeviceConfigLong(
+                    KEY_PENDING_COLD_START_CHECK_INTERVAL_MILLIS,
+                    DEFAULT_PENDING_COLD_START_CHECK_INTERVAL_MILLIS);
         }
 
         // TODO: migrate BroadcastRecord to accept a BroadcastConstants
@@ -499,6 +511,8 @@
                     MAX_CONSECUTIVE_NORMAL_DISPATCHES).println();
             pw.print(KEY_CORE_DEFER_UNTIL_ACTIVE,
                     CORE_DEFER_UNTIL_ACTIVE).println();
+            pw.print(KEY_PENDING_COLD_START_CHECK_INTERVAL_MILLIS,
+                    PENDING_COLD_START_CHECK_INTERVAL_MILLIS).println();
             pw.decreaseIndent();
             pw.println();
         }
diff --git a/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java b/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
index d6e692c..f180f02 100644
--- a/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
+++ b/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
@@ -248,6 +248,7 @@
     private static final int MSG_DELIVERY_TIMEOUT_HARD = 3;
     private static final int MSG_BG_ACTIVITY_START_TIMEOUT = 4;
     private static final int MSG_CHECK_HEALTH = 5;
+    private static final int MSG_CHECK_PENDING_COLD_START_VALIDITY = 6;
 
     private void enqueueUpdateRunningList() {
         mLocalHandler.removeMessages(MSG_UPDATE_RUNNING_LIST);
@@ -284,6 +285,10 @@
                 checkHealth();
                 return true;
             }
+            case MSG_CHECK_PENDING_COLD_START_VALIDITY: {
+                checkPendingColdStartValidity();
+                return true;
+            }
         }
         return false;
     };
@@ -450,10 +455,14 @@
                 // skip to look for another warm process
                 if (mRunningColdStart == null) {
                     mRunningColdStart = queue;
-                } else {
+                } else if (isPendingColdStartValid()) {
                     // Move to considering next runnable queue
                     queue = nextQueue;
                     continue;
+                } else {
+                    // Pending cold start is not valid, so clear it and move on.
+                    clearInvalidPendingColdStart();
+                    mRunningColdStart = queue;
                 }
             }
 
@@ -486,11 +495,46 @@
             mService.updateOomAdjPendingTargetsLocked(OOM_ADJ_REASON_START_RECEIVER);
         }
 
+        checkPendingColdStartValidity();
         checkAndRemoveWaitingFor();
 
         traceEnd(cookie);
     }
 
+    private boolean isPendingColdStartValid() {
+        if (mRunningColdStart.app.getPid() > 0) {
+            // If the process has already started, check if it wasn't killed.
+            return !mRunningColdStart.app.isKilled();
+        } else {
+            // Otherwise, check if the process start is still pending.
+            return mRunningColdStart.app.isPendingStart();
+        }
+    }
+
+    private void clearInvalidPendingColdStart() {
+        logw("Clearing invalid pending cold start: " + mRunningColdStart);
+        onApplicationCleanupLocked(mRunningColdStart.app);
+    }
+
+    private void checkPendingColdStartValidity() {
+        // There are a few cases where a starting process gets killed but AMS doesn't report
+        // this event. So, once we start waiting for a pending cold start, periodically check
+        // if the pending start is still valid and if not, clear it so that the queue doesn't
+        // keep waiting for the process start forever.
+        synchronized (mService) {
+            // If there is no pending cold start, then nothing to do.
+            if (mRunningColdStart == null) {
+                return;
+            }
+            if (isPendingColdStartValid()) {
+                mLocalHandler.sendEmptyMessageDelayed(MSG_CHECK_PENDING_COLD_START_VALIDITY,
+                        mConstants.PENDING_COLD_START_CHECK_INTERVAL_MILLIS);
+            } else {
+                clearInvalidPendingColdStart();
+            }
+        }
+    }
+
     @Override
     public boolean onApplicationAttachedLocked(@NonNull ProcessRecord app) {
         // Process records can be recycled, so always start by looking up the
diff --git a/services/core/java/com/android/server/am/OomAdjuster.java b/services/core/java/com/android/server/am/OomAdjuster.java
index 1e5f187..85a0185 100644
--- a/services/core/java/com/android/server/am/OomAdjuster.java
+++ b/services/core/java/com/android/server/am/OomAdjuster.java
@@ -837,7 +837,7 @@
      */
     @GuardedBy("mService")
     void enqueueOomAdjTargetLocked(ProcessRecord app) {
-        if (app != null) {
+        if (app != null && app.mState.getMaxAdj() > FOREGROUND_APP_ADJ) {
             mPendingProcessSet.add(app);
         }
     }
diff --git a/services/core/java/com/android/server/am/PendingIntentRecord.java b/services/core/java/com/android/server/am/PendingIntentRecord.java
index ab4fb46..202d407 100644
--- a/services/core/java/com/android/server/am/PendingIntentRecord.java
+++ b/services/core/java/com/android/server/am/PendingIntentRecord.java
@@ -349,21 +349,22 @@
      * use caller's BAL permission.
      */
     public static BackgroundStartPrivileges getBackgroundStartPrivilegesAllowedByCaller(
-            @Nullable ActivityOptions activityOptions, int callingUid) {
+            @Nullable ActivityOptions activityOptions, int callingUid,
+            @Nullable String callingPackage) {
         if (activityOptions == null) {
             // since the ActivityOptions were not created by the app itself, determine the default
             // for the app
-            return getDefaultBackgroundStartPrivileges(callingUid);
+            return getDefaultBackgroundStartPrivileges(callingUid, callingPackage);
         }
         return getBackgroundStartPrivilegesAllowedByCaller(activityOptions.toBundle(),
-                callingUid);
+                callingUid, callingPackage);
     }
 
     private static BackgroundStartPrivileges getBackgroundStartPrivilegesAllowedByCaller(
-            @Nullable Bundle options, int callingUid) {
+            @Nullable Bundle options, int callingUid, @Nullable String callingPackage) {
         if (options == null || !options.containsKey(
                         ActivityOptions.KEY_PENDING_INTENT_BACKGROUND_ACTIVITY_ALLOWED)) {
-            return getDefaultBackgroundStartPrivileges(callingUid);
+            return getDefaultBackgroundStartPrivileges(callingUid, callingPackage);
         }
         return options.getBoolean(ActivityOptions.KEY_PENDING_INTENT_BACKGROUND_ACTIVITY_ALLOWED)
                 ? BackgroundStartPrivileges.ALLOW_BAL
@@ -382,7 +383,7 @@
                     android.Manifest.permission.LOG_COMPAT_CHANGE
             })
     public static BackgroundStartPrivileges getDefaultBackgroundStartPrivileges(
-            int callingUid) {
+            int callingUid, @Nullable String callingPackage) {
         if (UserHandle.getAppId(callingUid) == Process.SYSTEM_UID) {
             // We temporarily allow BAL for system processes, while we verify that all valid use
             // cases are opted in explicitly to grant their BAL permission.
@@ -391,7 +392,9 @@
             // as soon as that app is upgraded (or removed) BAL would be blocked. (b/283138430)
             return BackgroundStartPrivileges.ALLOW_BAL;
         }
-        boolean isChangeEnabledForApp = CompatChanges.isChangeEnabled(
+        boolean isChangeEnabledForApp = callingPackage != null ? CompatChanges.isChangeEnabled(
+                DEFAULT_RESCIND_BAL_PRIVILEGES_FROM_PENDING_INTENT_SENDER, callingPackage,
+                UserHandle.getUserHandleForUid(callingUid)) : CompatChanges.isChangeEnabled(
                 DEFAULT_RESCIND_BAL_PRIVILEGES_FROM_PENDING_INTENT_SENDER, callingUid);
         if (isChangeEnabledForApp) {
             return BackgroundStartPrivileges.ALLOW_FGS;
@@ -647,7 +650,7 @@
         // temporarily allow receivers and services to open activities from background if the
         // PendingIntent.send() caller was foreground at the time of sendInner() call
         if (uid != callingUid && controller.mAtmInternal.isUidForeground(callingUid)) {
-            return getBackgroundStartPrivilegesAllowedByCaller(options, callingUid);
+            return getBackgroundStartPrivilegesAllowedByCaller(options, callingUid, null);
         }
         return BackgroundStartPrivileges.NONE;
     }
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index 355981a..d0b6cdc 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -45,7 +45,6 @@
 import android.annotation.UserIdInt;
 import android.app.ActivityManager;
 import android.app.ActivityManagerInternal;
-import android.app.ActivityThread;
 import android.app.AppGlobals;
 import android.app.AppOpsManager;
 import android.app.BroadcastOptions;
@@ -167,7 +166,6 @@
 import android.os.VibrationEffect;
 import android.os.Vibrator;
 import android.os.VibratorManager;
-import android.provider.DeviceConfig;
 import android.provider.Settings;
 import android.provider.Settings.System;
 import android.service.notification.ZenModeConfig;
@@ -187,10 +185,8 @@
 import android.view.accessibility.AccessibilityManager;
 import android.widget.Toast;
 
-
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.config.sysui.SystemUiDeviceConfigFlags;
 import com.android.internal.os.SomeArgs;
 import com.android.internal.util.DumpUtils;
 import com.android.internal.util.Preconditions;
@@ -252,7 +248,6 @@
             AudioSystemAdapter.OnVolRangeInitRequestListener {
 
     private static final String TAG = "AS.AudioService";
-    private static final boolean CONFIG_DEFAULT_VAL = false;
 
     private final AudioSystemAdapter mAudioSystem;
     private final SystemServerAdapter mSystemServer;
@@ -309,7 +304,7 @@
      * indicates whether STREAM_NOTIFICATION is aliased to STREAM_RING
      *     not final due to test method, see {@link #setNotifAliasRingForTest(boolean)}.
      */
-    private boolean mNotifAliasRing;
+    private boolean mNotifAliasRing = false;
 
     /**
      * Test method to temporarily override whether STREAM_NOTIFICATION is aliased to STREAM_RING,
@@ -1057,13 +1052,6 @@
         mUseVolumeGroupAliases = mContext.getResources().getBoolean(
                 com.android.internal.R.bool.config_handleVolumeAliasesUsingVolumeGroups);
 
-        mNotifAliasRing = !DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_SYSTEMUI,
-                SystemUiDeviceConfigFlags.VOLUME_SEPARATE_NOTIFICATION, false);
-
-        DeviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_SYSTEMUI,
-                ActivityThread.currentApplication().getMainExecutor(),
-                this::onDeviceConfigChange);
-
         // Initialize volume
         // Priority 1 - Android Property
         // Priority 2 - Audio Policy Service
@@ -1157,6 +1145,11 @@
                         MAX_STREAM_VOLUME[AudioSystem.STREAM_SYSTEM];
         }
 
+        int minAssistantVolume = SystemProperties.getInt("ro.config.assistant_vol_min", -1);
+        if (minAssistantVolume != -1) {
+            MIN_STREAM_VOLUME[AudioSystem.STREAM_ASSISTANT] = minAssistantVolume;
+        }
+
         // Read following properties to configure max volume (number of steps) and default volume
         //   for STREAM_NOTIFICATION and STREAM_RING:
         //      config_audio_notif_vol_default
@@ -1277,22 +1270,6 @@
     }
 
     /**
-     * Separating notification volume from ring is NOT of aliasing the corresponding streams
-     * @param properties
-     */
-    private void onDeviceConfigChange(DeviceConfig.Properties properties) {
-        Set<String> changeSet = properties.getKeyset();
-        if (changeSet.contains(SystemUiDeviceConfigFlags.VOLUME_SEPARATE_NOTIFICATION)) {
-            boolean newNotifAliasRing = !properties.getBoolean(
-                    SystemUiDeviceConfigFlags.VOLUME_SEPARATE_NOTIFICATION, CONFIG_DEFAULT_VAL);
-            if (mNotifAliasRing != newNotifAliasRing) {
-                mNotifAliasRing = newNotifAliasRing;
-                updateStreamVolumeAlias(true, TAG);
-            }
-        }
-    }
-
-    /**
      * Called by handling of MSG_INIT_STREAMS_VOLUMES
      */
     private void onInitStreamsAndVolumes() {
diff --git a/services/core/java/com/android/server/biometrics/sensors/UserAwareBiometricScheduler.java b/services/core/java/com/android/server/biometrics/sensors/UserAwareBiometricScheduler.java
index a486d16..694dfd2 100644
--- a/services/core/java/com/android/server/biometrics/sensors/UserAwareBiometricScheduler.java
+++ b/services/core/java/com/android/server/biometrics/sensors/UserAwareBiometricScheduler.java
@@ -70,9 +70,11 @@
 
                 // Set mStopUserClient to null when StopUserClient fails. Otherwise it's possible
                 // for that the queue will wait indefinitely until the field is cleared.
-                if (clientMonitor instanceof StopUserClient<?> && !success) {
-                    Slog.w(getTag(),
-                            "StopUserClient failed(), is the HAL stuck? Clearing mStopUserClient");
+                if (clientMonitor instanceof StopUserClient<?>) {
+                    if (!success) {
+                        Slog.w(getTag(), "StopUserClient failed(), is the HAL stuck? "
+                                + "Clearing mStopUserClient");
+                    }
                     mStopUserClient = null;
                 }
                 if (mCurrentOperation != null && mCurrentOperation.isFor(mOwner)) {
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/Sensor.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/Sensor.java
index ffbf4e1..2ad41c2 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/Sensor.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/Sensor.java
@@ -89,7 +89,7 @@
     @NonNull private final Map<Integer, Long> mAuthenticatorIds;
 
     @NonNull private final Supplier<AidlSession> mLazySession;
-    @Nullable private AidlSession mCurrentSession;
+    @Nullable AidlSession mCurrentSession;
 
     @VisibleForTesting
     public static class HalSessionCallback extends ISessionCallback.Stub {
@@ -486,7 +486,7 @@
     Sensor(@NonNull String tag, @NonNull FaceProvider provider, @NonNull Context context,
             @NonNull Handler handler, @NonNull FaceSensorPropertiesInternal sensorProperties,
             @NonNull LockoutResetDispatcher lockoutResetDispatcher,
-            @NonNull BiometricContext biometricContext) {
+            @NonNull BiometricContext biometricContext, AidlSession session) {
         mTag = tag;
         mProvider = provider;
         mContext = context;
@@ -549,6 +549,14 @@
         mLazySession = () -> mCurrentSession != null ? mCurrentSession : null;
     }
 
+    Sensor(@NonNull String tag, @NonNull FaceProvider provider, @NonNull Context context,
+            @NonNull Handler handler, @NonNull FaceSensorPropertiesInternal sensorProperties,
+            @NonNull LockoutResetDispatcher lockoutResetDispatcher,
+            @NonNull BiometricContext biometricContext) {
+        this(tag, provider, context, handler, sensorProperties, lockoutResetDispatcher,
+                biometricContext, null);
+    }
+
     @NonNull Supplier<AidlSession> getLazySession() {
         return mLazySession;
     }
@@ -557,7 +565,7 @@
         return mSensorProperties;
     }
 
-    @Nullable AidlSession getSessionForUser(int userId) {
+    @VisibleForTesting @Nullable AidlSession getSessionForUser(int userId) {
         if (mCurrentSession != null && mCurrentSession.getUserId() == userId) {
             return mCurrentSession;
         } else {
@@ -641,6 +649,8 @@
                     BiometricsProtoEnums.MODALITY_FACE,
                     BiometricsProtoEnums.ISSUE_HAL_DEATH,
                     -1 /* sensorId */);
+        } else if (client != null) {
+            client.cancel();
         }
 
         mScheduler.recordCrashState();
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/Sensor.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/Sensor.java
index c0dde72..56b85ce 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/Sensor.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/Sensor.java
@@ -90,7 +90,7 @@
     @NonNull private final LockoutCache mLockoutCache;
     @NonNull private final Map<Integer, Long> mAuthenticatorIds;
 
-    @Nullable private AidlSession mCurrentSession;
+    @Nullable AidlSession mCurrentSession;
     @NonNull private final Supplier<AidlSession> mLazySession;
 
     @VisibleForTesting
@@ -439,7 +439,7 @@
             @NonNull Handler handler, @NonNull FingerprintSensorPropertiesInternal sensorProperties,
             @NonNull LockoutResetDispatcher lockoutResetDispatcher,
             @NonNull GestureAvailabilityDispatcher gestureAvailabilityDispatcher,
-            @NonNull BiometricContext biometricContext) {
+            @NonNull BiometricContext biometricContext, AidlSession session) {
         mTag = tag;
         mProvider = provider;
         mContext = context;
@@ -501,6 +501,16 @@
                 });
         mAuthenticatorIds = new HashMap<>();
         mLazySession = () -> mCurrentSession != null ? mCurrentSession : null;
+        mCurrentSession = session;
+    }
+
+    Sensor(@NonNull String tag, @NonNull FingerprintProvider provider, @NonNull Context context,
+            @NonNull Handler handler, @NonNull FingerprintSensorPropertiesInternal sensorProperties,
+            @NonNull LockoutResetDispatcher lockoutResetDispatcher,
+            @NonNull GestureAvailabilityDispatcher gestureAvailabilityDispatcher,
+            @NonNull BiometricContext biometricContext) {
+        this(tag, provider, context, handler, sensorProperties, lockoutResetDispatcher,
+                gestureAvailabilityDispatcher, biometricContext, null);
     }
 
     @NonNull Supplier<AidlSession> getLazySession() {
@@ -599,6 +609,8 @@
                     BiometricsProtoEnums.MODALITY_FINGERPRINT,
                     BiometricsProtoEnums.ISSUE_HAL_DEATH,
                     -1 /* sensorId */);
+        } else if (client != null) {
+            client.cancel();
         }
 
         mScheduler.recordCrashState();
diff --git a/services/core/java/com/android/server/locksettings/LockSettingsService.java b/services/core/java/com/android/server/locksettings/LockSettingsService.java
index 20f0697..2e62ef4 100644
--- a/services/core/java/com/android/server/locksettings/LockSettingsService.java
+++ b/services/core/java/com/android/server/locksettings/LockSettingsService.java
@@ -889,22 +889,31 @@
 
     }
 
-    private void migrateOldDataAfterSystemReady() {
-        // Migrate the FRP credential to the persistent data block
+    @VisibleForTesting
+    void migrateOldDataAfterSystemReady() {
+        // Write the FRP persistent data block if needed.
+        //
+        // The original purpose of this code was to write the FRP block for the first time, when
+        // upgrading from Android 8.1 or earlier which didn't use the FRP block.  This code has
+        // since been repurposed to also fix the "bad" (non-forwards-compatible) FRP block written
+        // by Android 14 Beta 2.  For this reason, the database key used here has been renamed from
+        // "migrated_frp" to "migrated_frp2" to cause migrateFrpCredential() to run again on devices
+        // where it had run before.
         if (LockPatternUtils.frpCredentialEnabled(mContext)
-                && !getBoolean("migrated_frp", false, 0)) {
+                && !getBoolean("migrated_frp2", false, 0)) {
             migrateFrpCredential();
-            setBoolean("migrated_frp", true, 0);
+            setBoolean("migrated_frp2", true, 0);
         }
     }
 
     /**
-     * Migrate the credential for the FRP credential owner user if the following are satisfied:
-     * - the user has a secure credential
-     * - the FRP credential is not set up
+     * Write the FRP persistent data block if the following are satisfied:
+     * - the user who owns the FRP credential has a nonempty credential
+     * - the FRP persistent data block doesn't exist or uses the "bad" format from Android 14 Beta 2
      */
     private void migrateFrpCredential() {
-        if (mStorage.readPersistentDataBlock() != PersistentData.NONE) {
+        PersistentData data = mStorage.readPersistentDataBlock();
+        if (data != PersistentData.NONE && !data.isBadFormatFromAndroid14Beta()) {
             return;
         }
         for (UserInfo userInfo : mUserManager.getUsers()) {
diff --git a/services/core/java/com/android/server/locksettings/LockSettingsStorage.java b/services/core/java/com/android/server/locksettings/LockSettingsStorage.java
index 731ecad..2fa637e 100644
--- a/services/core/java/com/android/server/locksettings/LockSettingsStorage.java
+++ b/services/core/java/com/android/server/locksettings/LockSettingsStorage.java
@@ -606,6 +606,11 @@
             this.payload = payload;
         }
 
+        public boolean isBadFormatFromAndroid14Beta() {
+            return (this.type == TYPE_SP_GATEKEEPER || this.type == TYPE_SP_WEAVER)
+                && SyntheticPasswordManager.PasswordData.isBadFormatFromAndroid14Beta(this.payload);
+        }
+
         public static PersistentData fromBytes(byte[] frpData) {
             if (frpData == null || frpData.length == 0) {
                 return NONE;
diff --git a/services/core/java/com/android/server/locksettings/SyntheticPasswordManager.java b/services/core/java/com/android/server/locksettings/SyntheticPasswordManager.java
index 65e7a00..66f862a 100644
--- a/services/core/java/com/android/server/locksettings/SyntheticPasswordManager.java
+++ b/services/core/java/com/android/server/locksettings/SyntheticPasswordManager.java
@@ -370,6 +370,15 @@
             return result;
         }
 
+        /**
+         * Returns true if the given serialized PasswordData begins with the value 2 as a short.
+         * This detects the "bad" (non-forwards-compatible) PasswordData format that was temporarily
+         * used during development of Android 14.  For more details, see fromBytes() below.
+         */
+        public static boolean isBadFormatFromAndroid14Beta(byte[] data) {
+            return data != null && data.length >= 2 && data[0] == 0 && data[1] == 2;
+        }
+
         public static PasswordData fromBytes(byte[] data) {
             PasswordData result = new PasswordData();
             ByteBuffer buffer = ByteBuffer.allocate(data.length);
diff --git a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
index 9add537..a079875 100644
--- a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
+++ b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
@@ -82,6 +82,7 @@
 import android.os.IInterface;
 import android.os.IRemoteCallback;
 import android.os.ParcelFileDescriptor;
+import android.os.Process;
 import android.os.RemoteCallbackList;
 import android.os.RemoteException;
 import android.os.ResultReceiver;
@@ -2514,6 +2515,7 @@
      * Propagate a wake event to the wallpaper engine(s).
      */
     public void notifyWakingUp(int x, int y, @NonNull Bundle extras) {
+        checkCallerIsSystemOrSystemUi();
         synchronized (mLock) {
             if (mIsLockscreenLiveWallpaperEnabled) {
                 for (WallpaperData data : getActiveWallpapers()) {
@@ -2551,6 +2553,7 @@
      * Propagate a sleep event to the wallpaper engine(s).
      */
     public void notifyGoingToSleep(int x, int y, @NonNull Bundle extras) {
+        checkCallerIsSystemOrSystemUi();
         synchronized (mLock) {
             if (mIsLockscreenLiveWallpaperEnabled) {
                 for (WallpaperData data : getActiveWallpapers()) {
@@ -3684,6 +3687,14 @@
                 mActivityManager.getPackageImportance(callingPackage) == IMPORTANCE_FOREGROUND);
     }
 
+    /** Check that the caller is either system_server or systemui */
+    private void checkCallerIsSystemOrSystemUi() {
+        if (Binder.getCallingUid() != Process.myUid() && mContext.checkCallingPermission(
+                android.Manifest.permission.STATUS_BAR_SERVICE) != PERMISSION_GRANTED) {
+            throw new SecurityException("Access denied: only system processes can call this");
+        }
+    }
+
     /**
      * Certain user types do not support wallpapers (e.g. managed profiles). The check is
      * implemented through through the OP_WRITE_WALLPAPER AppOp.
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index 1360a95..750ed98 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -5342,6 +5342,12 @@
         return null;
     }
 
+    /**
+     * Returns the {@link WindowProcessController} for the app process for the given uid and pid.
+     *
+     * If no such {@link WindowProcessController} is found, it does not belong to an app, or the
+     * pid does not match the uid {@code null} is returned.
+     */
     WindowProcessController getProcessController(int pid, int uid) {
         final WindowProcessController proc = mProcessMap.getProcess(pid);
         if (proc == null) return null;
@@ -5351,6 +5357,27 @@
         return null;
     }
 
+    /**
+     * Returns the package name if (and only if) the package name can be uniquely determined.
+     * Otherwise returns {@code null}.
+     *
+     * The provided pid must match the provided uid, otherwise this also returns null.
+     */
+    @Nullable String getPackageNameIfUnique(int uid, int pid) {
+        final WindowProcessController proc = mProcessMap.getProcess(pid);
+        if (proc == null || proc.mUid != uid) {
+            Slog.w(TAG, "callingPackage for (uid=" + uid + ", pid=" + pid + ") has no WPC");
+            return null;
+        }
+        List<String> realCallingPackages = proc.getPackageList();
+        if (realCallingPackages.size() != 1) {
+            Slog.w(TAG, "callingPackage for (uid=" + uid + ", pid=" + pid + ") is ambiguous: "
+                    + realCallingPackages);
+            return null;
+        }
+        return realCallingPackages.get(0);
+    }
+
     /** A uid is considered to be foreground if it has a visible non-toast window. */
     @HotPath(caller = HotPath.START_SERVICE)
     boolean hasActiveVisibleWindow(int uid) {
diff --git a/services/core/java/com/android/server/wm/BackgroundActivityStartController.java b/services/core/java/com/android/server/wm/BackgroundActivityStartController.java
index dc49e8c..b216578 100644
--- a/services/core/java/com/android/server/wm/BackgroundActivityStartController.java
+++ b/services/core/java/com/android/server/wm/BackgroundActivityStartController.java
@@ -180,7 +180,8 @@
             Intent intent,
             ActivityOptions checkedOptions) {
         return checkBackgroundActivityStart(callingUid, callingPid, callingPackage,
-                realCallingUid, realCallingPid, callerApp, originatingPendingIntent,
+                realCallingUid, realCallingPid,
+                callerApp, originatingPendingIntent,
                 backgroundStartPrivileges, intent, checkedOptions) == BAL_BLOCK;
     }
 
@@ -288,11 +289,13 @@
             }
         }
 
+        String realCallingPackage = mService.getPackageNameIfUnique(realCallingUid, realCallingPid);
+
         // Legacy behavior allows to use caller foreground state to bypass BAL restriction.
         // The options here are the options passed by the sender and not those on the intent.
         final BackgroundStartPrivileges balAllowedByPiSender =
                 PendingIntentRecord.getBackgroundStartPrivilegesAllowedByCaller(
-                        checkedOptions, realCallingUid);
+                        checkedOptions, realCallingUid, realCallingPackage);
 
         final boolean logVerdictChangeByPiDefaultChange = checkedOptions == null
                 || checkedOptions.getPendingIntentBackgroundActivityStartMode()
@@ -460,8 +463,11 @@
         // If we are here, it means all exemptions not based on PI sender failed, so we'll block
         // unless resultIfPiSenderAllowsBal is an allow and the PI sender allows BAL
 
-        String realCallingPackage = callingUid == realCallingUid ? callingPackage :
-                mService.mContext.getPackageManager().getNameForUid(realCallingUid);
+        if (realCallingPackage == null) {
+            realCallingPackage = (callingUid == realCallingUid ? callingPackage :
+                    mService.mContext.getPackageManager().getNameForUid(realCallingUid))
+                    + "[debugOnly]";
+        }
 
         String stateDumpLog = " [callingPackage: " + callingPackage
                 + "; callingUid: " + callingUid
diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java
index d0ca8e3..35e891c 100644
--- a/services/core/java/com/android/server/wm/DisplayPolicy.java
+++ b/services/core/java/com/android/server/wm/DisplayPolicy.java
@@ -271,13 +271,13 @@
     private WindowState mSystemUiControllingWindow;
 
     // Candidate window to determine the color of navigation bar. The window needs to be top
-    // fullscreen-app windows or dim layers that are intersecting with the window frame of status
-    // bar.
+    // fullscreen-app windows or dim layers that are intersecting with the window frame of
+    // navigation bar.
     private WindowState mNavBarColorWindowCandidate;
 
-    // The window to determine opacity and background of translucent navigation bar. The window
-    // needs to be opaque.
-    private WindowState mNavBarBackgroundWindow;
+    // Candidate window to determine opacity and background of translucent navigation bar.
+    // The window frame must intersect the frame of navigation bar.
+    private WindowState mNavBarBackgroundWindowCandidate;
 
     /**
      * A collection of {@link AppearanceRegion} to indicate that which region of status bar applies
@@ -961,12 +961,6 @@
         if (!win.mSession.mCanSetUnrestrictedGestureExclusion) {
             attrs.privateFlags &= ~PRIVATE_FLAG_UNRESTRICTED_GESTURE_EXCLUSION;
         }
-
-        final InsetsSourceProvider provider = win.getControllableInsetProvider();
-        if (provider != null && provider.getSource().insetsRoundedCornerFrame()
-                != attrs.insetsRoundedCornerFrame) {
-            provider.getSource().setInsetsRoundedCornerFrame(attrs.insetsRoundedCornerFrame);
-        }
     }
 
     /**
@@ -1104,9 +1098,11 @@
                 } else {
                     overrideProviders = null;
                 }
-                mDisplayContent.getInsetsStateController().getOrCreateSourceProvider(
-                        provider.getId(), provider.getType()).setWindowContainer(
-                                win, frameProvider, overrideProviders);
+                final InsetsSourceProvider sourceProvider = mDisplayContent
+                        .getInsetsStateController().getOrCreateSourceProvider(provider.getId(),
+                                provider.getType());
+                sourceProvider.getSource().setFlags(provider.getFlags());
+                sourceProvider.setWindowContainer(win, frameProvider, overrideProviders);
                 mInsetsSourceWindowsExceptIme.add(win);
             }
         }
@@ -1387,7 +1383,7 @@
         mBottomGestureHost = null;
         mTopFullscreenOpaqueWindowState = null;
         mNavBarColorWindowCandidate = null;
-        mNavBarBackgroundWindow = null;
+        mNavBarBackgroundWindowCandidate = null;
         mStatusBarAppearanceRegionList.clear();
         mLetterboxDetails.clear();
         mStatusBarBackgroundWindows.clear();
@@ -1514,8 +1510,8 @@
                     mNavBarColorWindowCandidate = win;
                     addSystemBarColorApp(win);
                 }
-                if (mNavBarBackgroundWindow == null) {
-                    mNavBarBackgroundWindow = win;
+                if (mNavBarBackgroundWindowCandidate == null) {
+                    mNavBarBackgroundWindowCandidate = win;
                 }
             }
 
@@ -1539,12 +1535,19 @@
             }
             if (isOverlappingWithNavBar(win) && mNavBarColorWindowCandidate == null) {
                 mNavBarColorWindowCandidate = win;
+                addSystemBarColorApp(win);
             }
-        } else if (appWindow && attached == null && mNavBarColorWindowCandidate == null
+        } else if (appWindow && attached == null
+                && (mNavBarColorWindowCandidate == null || mNavBarBackgroundWindowCandidate == null)
                 && win.getFrame().contains(
                         getBarContentFrameForWindow(win, Type.navigationBars()))) {
-            mNavBarColorWindowCandidate = win;
-            addSystemBarColorApp(win);
+            if (mNavBarColorWindowCandidate == null) {
+                mNavBarColorWindowCandidate = win;
+                addSystemBarColorApp(win);
+            }
+            if (mNavBarBackgroundWindowCandidate == null) {
+                mNavBarBackgroundWindowCandidate = win;
+            }
         }
     }
 
@@ -2465,7 +2468,7 @@
         return win.isFullyTransparentBarAllowed(getBarContentFrameForWindow(win, type));
     }
 
-    private boolean drawsBarBackground(WindowState win) {
+    private static boolean drawsBarBackground(WindowState win) {
         if (win == null) {
             return true;
         }
@@ -2505,7 +2508,11 @@
      */
     private int configureNavBarOpacity(int appearance, boolean multiWindowTaskVisible,
             boolean freeformRootTaskVisible) {
-        final boolean drawBackground = drawsBarBackground(mNavBarBackgroundWindow);
+        final WindowState navBackgroundWin = chooseNavigationBackgroundWindow(
+                mNavBarBackgroundWindowCandidate,
+                mDisplayContent.mInputMethodWindow,
+                mNavigationBarPosition);
+        final boolean drawBackground = navBackgroundWin != null;
 
         if (mNavBarOpacityMode == NAV_BAR_FORCE_TRANSPARENT) {
             if (drawBackground) {
@@ -2525,7 +2532,7 @@
             }
         }
 
-        if (!isFullyTransparentAllowed(mNavBarBackgroundWindow, Type.navigationBars())) {
+        if (!isFullyTransparentAllowed(navBackgroundWin, Type.navigationBars())) {
             appearance |= APPEARANCE_SEMI_TRANSPARENT_NAVIGATION_BARS;
         }
 
@@ -2536,6 +2543,20 @@
         return appearance & ~APPEARANCE_OPAQUE_NAVIGATION_BARS;
     }
 
+    @VisibleForTesting
+    @Nullable
+    static WindowState chooseNavigationBackgroundWindow(WindowState candidate,
+            WindowState imeWindow, @NavigationBarPosition int navBarPosition) {
+        if (imeWindow != null && imeWindow.isVisible() && navBarPosition == NAV_BAR_BOTTOM
+                && drawsBarBackground(imeWindow)) {
+            return imeWindow;
+        }
+        if (drawsBarBackground(candidate)) {
+            return candidate;
+        }
+        return null;
+    }
+
     private boolean isImmersiveMode(WindowState win) {
         if (win == null) {
             return false;
@@ -2708,9 +2729,9 @@
             pw.print(prefix); pw.print("mNavBarColorWindowCandidate=");
             pw.println(mNavBarColorWindowCandidate);
         }
-        if (mNavBarBackgroundWindow != null) {
-            pw.print(prefix); pw.print("mNavBarBackgroundWindow=");
-            pw.println(mNavBarBackgroundWindow);
+        if (mNavBarBackgroundWindowCandidate != null) {
+            pw.print(prefix); pw.print("mNavBarBackgroundWindowCandidate=");
+            pw.println(mNavBarBackgroundWindowCandidate);
         }
         if (mLastStatusBarAppearanceRegions != null) {
             pw.print(prefix); pw.println("mLastStatusBarAppearanceRegions=");
diff --git a/services/core/java/com/android/server/wm/InsetsSourceProvider.java b/services/core/java/com/android/server/wm/InsetsSourceProvider.java
index b7eaf25..7f845e6 100644
--- a/services/core/java/com/android/server/wm/InsetsSourceProvider.java
+++ b/services/core/java/com/android/server/wm/InsetsSourceProvider.java
@@ -175,7 +175,7 @@
         if (windowContainer == null) {
             setServerVisible(false);
             mSource.setVisibleFrame(null);
-            mSource.setInsetsRoundedCornerFrame(false);
+            mSource.setFlags(0, 0xffffffff);
             mSourceFrame.setEmpty();
         } else {
             mWindowContainer.getInsetsSourceProviders().put(mSource.getId(), this);
diff --git a/services/core/java/com/android/server/wm/LetterboxUiController.java b/services/core/java/com/android/server/wm/LetterboxUiController.java
index d83c861..c243988 100644
--- a/services/core/java/com/android/server/wm/LetterboxUiController.java
+++ b/services/core/java/com/android/server/wm/LetterboxUiController.java
@@ -1428,7 +1428,8 @@
         for (int i = state.sourceSize() - 1; i >= 0; i--) {
             final InsetsSource source = state.sourceAt(i);
             if (source.getType() == WindowInsets.Type.navigationBars()
-                    && source.insetsRoundedCornerFrame() && source.isVisible()) {
+                    && source.hasFlags(InsetsSource.FLAG_INSETS_ROUNDED_CORNER)
+                    && source.isVisible()) {
                 return source;
             }
         }
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index 7b7dd85..ce4f445 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -49,6 +49,7 @@
 import static android.provider.Settings.Secure.USER_SETUP_COMPLETE;
 import static android.view.Display.DEFAULT_DISPLAY;
 import static android.view.Display.INVALID_DISPLAY;
+import static android.view.InsetsSource.FLAG_INSETS_ROUNDED_CORNER;
 import static android.view.SurfaceControl.METADATA_TASK_ID;
 import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
 import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES;
@@ -2862,7 +2863,7 @@
                     getDisplayContent().getInsetsStateController().getRawInsetsState();
             for (int i = state.sourceSize() - 1; i >= 0; i--) {
                 final InsetsSource source = state.sourceAt(i);
-                if (source.insetsRoundedCornerFrame()) {
+                if (source.hasFlags(FLAG_INSETS_ROUNDED_CORNER)) {
                     animationBounds.inset(source.calculateVisibleInsets(animationBounds));
                 }
             }
diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java
index c763cfa..0ce794f 100644
--- a/services/core/java/com/android/server/wm/Transition.java
+++ b/services/core/java/com/android/server/wm/Transition.java
@@ -382,11 +382,7 @@
             // Add FLAG_ABOVE_TRANSIENT_LAUNCH to the tree of transient-hide tasks,
             // so ChangeInfo#hasChanged() can return true to report the transition info.
             for (int i = mChanges.size() - 1; i >= 0; --i) {
-                final WindowContainer<?> wc = mChanges.keyAt(i);
-                if (wc.asTaskFragment() == null && wc.asActivityRecord() == null) continue;
-                if (isInTransientHide(wc)) {
-                    mChanges.valueAt(i).mFlags |= ChangeInfo.FLAG_ABOVE_TRANSIENT_LAUNCH;
-                }
+                updateTransientFlags(mChanges.valueAt(i));
             }
         }
         ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, "Transition %d: Set %s as "
@@ -581,7 +577,9 @@
         for (WindowContainer<?> curr = getAnimatableParent(wc);
                 curr != null && !mChanges.containsKey(curr);
                 curr = getAnimatableParent(curr)) {
-            mChanges.put(curr, new ChangeInfo(curr));
+            final ChangeInfo info = new ChangeInfo(curr);
+            updateTransientFlags(info);
+            mChanges.put(curr, info);
             if (isReadyGroup(curr)) {
                 mReadyTracker.addGroup(curr);
                 ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, " Creating Ready-group for"
@@ -600,6 +598,7 @@
         ChangeInfo info = mChanges.get(wc);
         if (info == null) {
             info = new ChangeInfo(wc);
+            updateTransientFlags(info);
             mChanges.put(wc, info);
         }
         mParticipants.add(wc);
@@ -615,6 +614,14 @@
         }
     }
 
+    private void updateTransientFlags(@NonNull ChangeInfo info) {
+        final WindowContainer<?> wc = info.mContainer;
+        // Only look at tasks, taskfragments, or activities
+        if (wc.asTaskFragment() == null && wc.asActivityRecord() == null) return;
+        if (!isInTransientHide(wc)) return;
+        info.mFlags |= ChangeInfo.FLAG_ABOVE_TRANSIENT_LAUNCH;
+    }
+
     private void recordDisplay(DisplayContent dc) {
         if (dc == null || mTargetDisplays.contains(dc)) return;
         mTargetDisplays.add(dc);
diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java
index 0152666..4bc4c26 100644
--- a/services/core/java/com/android/server/wm/WindowContainer.java
+++ b/services/core/java/com/android/server/wm/WindowContainer.java
@@ -4141,7 +4141,7 @@
                     getDisplayContent().getInsetsStateController().getSourceProviders();
             for (int i = providers.size(); i >= 0; i--) {
                 final InsetsSourceProvider insetProvider = providers.valueAt(i);
-                if (!insetProvider.getSource().insetsRoundedCornerFrame()) {
+                if (!insetProvider.getSource().hasFlags(InsetsSource.FLAG_INSETS_ROUNDED_CORNER)) {
                     return;
                 }
 
diff --git a/services/core/java/com/android/server/wm/WindowProcessController.java b/services/core/java/com/android/server/wm/WindowProcessController.java
index dbd9e4b..3672820 100644
--- a/services/core/java/com/android/server/wm/WindowProcessController.java
+++ b/services/core/java/com/android/server/wm/WindowProcessController.java
@@ -721,6 +721,12 @@
         }
     }
 
+    List<String> getPackageList() {
+        synchronized (mPkgList) {
+            return new ArrayList<>(mPkgList);
+        }
+    }
+
     void addActivityIfNeeded(ActivityRecord r) {
         // even if we already track this activity, note down that it has been launched
         setLastActivityLaunchTime(r);
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index af84180..991248a 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -3255,6 +3255,12 @@
                 Slog.d(TAG, "ContentCaptureService disabled because resource is not overlaid");
                 return;
             }
+            if (!deviceHasConfigString(context, R.string.config_defaultContentProtectionService)) {
+                Slog.d(
+                        TAG,
+                        "ContentProtectionService disabled because resource is not overlaid,"
+                            + " ContentCaptureService still enabled");
+            }
         }
 
         t.traceBegin("StartContentCaptureService");
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java
index ad5f0d7..6365764 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java
@@ -269,7 +269,9 @@
                     deliverRes = res;
                     break;
             }
+            res.setPendingStart(true);
             mHandlerThread.getThreadHandler().post(() -> {
+                res.setPendingStart(false);
                 synchronized (mAms) {
                     switch (behavior) {
                         case SUCCESS:
@@ -281,6 +283,10 @@
                             mActiveProcesses.remove(deliverRes);
                             mQueue.onApplicationTimeoutLocked(deliverRes);
                             break;
+                        case KILLED_WITHOUT_NOTIFY:
+                            mActiveProcesses.remove(res);
+                            res.setKilled(true);
+                            break;
                         default:
                             throw new UnsupportedOperationException();
                     }
@@ -310,6 +316,7 @@
         mConstants = new BroadcastConstants(Settings.Global.BROADCAST_FG_CONSTANTS);
         mConstants.TIMEOUT = 100;
         mConstants.ALLOW_BG_ACTIVITY_START_TIMEOUT = 0;
+        mConstants.PENDING_COLD_START_CHECK_INTERVAL_MILLIS = 500;
 
         mSkipPolicy = spy(new BroadcastSkipPolicy(mAms));
         doReturn(null).when(mSkipPolicy).shouldSkipMessage(any(), any());
@@ -381,6 +388,8 @@
         FAIL_TIMEOUT_PREDECESSOR,
         /** Process fails by immediately returning null */
         FAIL_NULL,
+        /** Process is killed without reporting to BroadcastQueue */
+        KILLED_WITHOUT_NOTIFY,
     }
 
     private enum ProcessBehavior {
@@ -522,6 +531,11 @@
         return info;
     }
 
+    static BroadcastFilter withPriority(BroadcastFilter filter, int priority) {
+        filter.setPriority(priority);
+        return filter;
+    }
+
     static ResolveInfo makeManifestReceiver(String packageName, String name) {
         return makeManifestReceiver(packageName, name, UserHandle.USER_SYSTEM);
     }
@@ -1261,6 +1275,46 @@
                 new ComponentName(PACKAGE_GREEN, CLASS_GREEN));
     }
 
+    /**
+     * Verify that when BroadcastQueue doesn't get notified when a process gets killed, it
+     * doesn't get stuck.
+     */
+    @Test
+    public void testKillWithoutNotify() throws Exception {
+        final ProcessRecord callerApp = makeActiveProcessRecord(PACKAGE_RED);
+        final ProcessRecord receiverBlueApp = makeActiveProcessRecord(PACKAGE_BLUE);
+
+        mNextProcessStartBehavior.set(ProcessStartBehavior.KILLED_WITHOUT_NOTIFY);
+
+        final Intent airplane = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED);
+        enqueueBroadcast(makeBroadcastRecord(airplane, callerApp, List.of(
+                withPriority(makeManifestReceiver(PACKAGE_GREEN, CLASS_GREEN), 10),
+                withPriority(makeRegisteredReceiver(receiverBlueApp), 5),
+                withPriority(makeManifestReceiver(PACKAGE_YELLOW, CLASS_YELLOW), 0))));
+
+        final Intent timezone = new Intent(Intent.ACTION_TIMEZONE_CHANGED);
+        enqueueBroadcast(makeBroadcastRecord(timezone, callerApp,
+                List.of(makeManifestReceiver(PACKAGE_ORANGE, CLASS_ORANGE))));
+
+        waitForIdle();
+        final ProcessRecord receiverGreenApp = mAms.getProcessRecordLocked(PACKAGE_GREEN,
+                getUidForPackage(PACKAGE_GREEN));
+        final ProcessRecord receiverYellowApp = mAms.getProcessRecordLocked(PACKAGE_YELLOW,
+                getUidForPackage(PACKAGE_YELLOW));
+        final ProcessRecord receiverOrangeApp = mAms.getProcessRecordLocked(PACKAGE_ORANGE,
+                getUidForPackage(PACKAGE_ORANGE));
+
+        if (mImpl == Impl.MODERN) {
+            // Modern queue does not retry sending a broadcast once any broadcast delivery fails.
+            assertNull(receiverGreenApp);
+        } else {
+            verifyScheduleReceiver(times(1), receiverGreenApp, airplane);
+        }
+        verifyScheduleRegisteredReceiver(times(1), receiverBlueApp, airplane);
+        verifyScheduleReceiver(times(1), receiverYellowApp, airplane);
+        verifyScheduleReceiver(times(1), receiverOrangeApp, timezone);
+    }
+
     @Test
     public void testCold_Success() throws Exception {
         doCold(ProcessStartBehavior.SUCCESS);
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/SensorTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/SensorTest.java
index 25bd9bc..be9f52e 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/SensorTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/SensorTest.java
@@ -17,12 +17,14 @@
 package com.android.server.biometrics.sensors.face.aidl;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNull;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.anyLong;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
@@ -41,6 +43,7 @@
 import com.android.server.biometrics.log.BiometricContext;
 import com.android.server.biometrics.log.BiometricLogger;
 import com.android.server.biometrics.sensors.AuthSessionCoordinator;
+import com.android.server.biometrics.sensors.BaseClientMonitor;
 import com.android.server.biometrics.sensors.BiometricScheduler;
 import com.android.server.biometrics.sensors.LockoutCache;
 import com.android.server.biometrics.sensors.LockoutResetDispatcher;
@@ -82,6 +85,10 @@
     private AuthSessionCoordinator mAuthSessionCoordinator;
     @Mock
     FaceProvider mFaceProvider;
+    @Mock
+    BaseClientMonitor mClientMonitor;
+    @Mock
+    AidlSession mCurrentSession;
 
     private final TestLooper mLooper = new TestLooper();
     private final LockoutCache mLockoutCache = new LockoutCache();
@@ -161,6 +168,39 @@
         assertNull(sensor.getSessionForUser(USER_ID));
     }
 
+    @Test
+    public void onBinderDied_cancelNonInterruptableClient() {
+        mLooper.dispatchAll();
+
+        when(mCurrentSession.getUserId()).thenReturn(USER_ID);
+        when(mClientMonitor.getTargetUserId()).thenReturn(USER_ID);
+        when(mClientMonitor.isInterruptable()).thenReturn(false);
+
+        final SensorProps sensorProps = new SensorProps();
+        sensorProps.commonProps = new CommonProps();
+        sensorProps.commonProps.sensorId = 1;
+        final FaceSensorPropertiesInternal internalProp = new FaceSensorPropertiesInternal(
+                sensorProps.commonProps.sensorId, sensorProps.commonProps.sensorStrength,
+                sensorProps.commonProps.maxEnrollmentsPerUser, null,
+                sensorProps.sensorType, sensorProps.supportsDetectInteraction,
+                sensorProps.halControlsPreview, false /* resetLockoutRequiresChallenge */);
+        final Sensor sensor = new Sensor("SensorTest", mFaceProvider, mContext, null,
+                internalProp, mLockoutResetDispatcher, mBiometricContext, mCurrentSession);
+        mScheduler = (UserAwareBiometricScheduler) sensor.getScheduler();
+        sensor.mCurrentSession = new AidlSession(0, mock(ISession.class),
+                USER_ID, mHalCallback);
+
+        mScheduler.scheduleClientMonitor(mClientMonitor);
+
+        assertNotNull(mScheduler.getCurrentClient());
+
+        sensor.onBinderDied();
+
+        verify(mClientMonitor).cancel();
+        assertNull(sensor.getSessionForUser(USER_ID));
+        assertNull(mScheduler.getCurrentClient());
+    }
+
     private void verifyNotLocked() {
         assertEquals(LockoutTracker.LOCKOUT_NONE, mLockoutCache.getLockoutModeForUser(USER_ID));
         verify(mLockoutResetDispatcher).notifyLockoutResetCallbacks(eq(SENSOR_ID));
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/SensorTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/SensorTest.java
index 0c13466..15d7601 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/SensorTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/SensorTest.java
@@ -17,17 +17,23 @@
 package com.android.server.biometrics.sensors.fingerprint.aidl;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.anyLong;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
 import android.content.Context;
 import android.hardware.biometrics.IBiometricService;
+import android.hardware.biometrics.common.CommonProps;
+import android.hardware.biometrics.face.SensorProps;
 import android.hardware.biometrics.fingerprint.ISession;
+import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
 import android.os.Handler;
 import android.os.test.TestLooper;
 import android.platform.test.annotations.Presubmit;
@@ -37,11 +43,13 @@
 import com.android.server.biometrics.log.BiometricContext;
 import com.android.server.biometrics.log.BiometricLogger;
 import com.android.server.biometrics.sensors.AuthSessionCoordinator;
+import com.android.server.biometrics.sensors.BaseClientMonitor;
 import com.android.server.biometrics.sensors.BiometricScheduler;
 import com.android.server.biometrics.sensors.LockoutCache;
 import com.android.server.biometrics.sensors.LockoutResetDispatcher;
 import com.android.server.biometrics.sensors.LockoutTracker;
 import com.android.server.biometrics.sensors.UserAwareBiometricScheduler;
+import com.android.server.biometrics.sensors.fingerprint.GestureAvailabilityDispatcher;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -76,6 +84,14 @@
     private BiometricContext mBiometricContext;
     @Mock
     private AuthSessionCoordinator mAuthSessionCoordinator;
+    @Mock
+    FingerprintProvider mFingerprintProvider;
+    @Mock
+    GestureAvailabilityDispatcher mGestureAvailabilityDispatcher;
+    @Mock
+    private AidlSession mCurrentSession;
+    @Mock
+    private BaseClientMonitor mClientMonitor;
 
     private final TestLooper mLooper = new TestLooper();
     private final LockoutCache mLockoutCache = new LockoutCache();
@@ -130,6 +146,40 @@
         verifyNotLocked();
     }
 
+    @Test
+    public void onBinderDied_cancelNonInterruptableClient() {
+        mLooper.dispatchAll();
+
+        when(mCurrentSession.getUserId()).thenReturn(USER_ID);
+        when(mClientMonitor.getTargetUserId()).thenReturn(USER_ID);
+        when(mClientMonitor.isInterruptable()).thenReturn(false);
+
+        final SensorProps sensorProps = new SensorProps();
+        sensorProps.commonProps = new CommonProps();
+        sensorProps.commonProps.sensorId = 1;
+        final FingerprintSensorPropertiesInternal internalProp = new
+                FingerprintSensorPropertiesInternal(
+                        sensorProps.commonProps.sensorId, sensorProps.commonProps.sensorStrength,
+                        sensorProps.commonProps.maxEnrollmentsPerUser, null,
+                        sensorProps.sensorType, false /* resetLockoutRequiresHardwareAuthToken */);
+        final Sensor sensor = new Sensor("SensorTest", mFingerprintProvider, mContext,
+                null /* handler */, internalProp, mLockoutResetDispatcher,
+                mGestureAvailabilityDispatcher, mBiometricContext, mCurrentSession);
+        mScheduler = (UserAwareBiometricScheduler) sensor.getScheduler();
+        sensor.mCurrentSession = new AidlSession(0, mock(ISession.class),
+                USER_ID, mHalCallback);
+
+        mScheduler.scheduleClientMonitor(mClientMonitor);
+
+        assertNotNull(mScheduler.getCurrentClient());
+
+        sensor.onBinderDied();
+
+        verify(mClientMonitor).cancel();
+        assertNull(sensor.getSessionForUser(USER_ID));
+        assertNull(mScheduler.getCurrentClient());
+    }
+
     private void verifyNotLocked() {
         assertEquals(LockoutTracker.LOCKOUT_NONE, mLockoutCache.getLockoutModeForUser(USER_ID));
         verify(mLockoutResetDispatcher).notifyLockoutResetCallbacks(eq(SENSOR_ID));
diff --git a/services/tests/servicestests/src/com/android/server/contentprotection/ContentProtectionBlocklistManagerTest.java b/services/tests/servicestests/src/com/android/server/contentprotection/ContentProtectionBlocklistManagerTest.java
new file mode 100644
index 0000000..ba9956a
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/contentprotection/ContentProtectionBlocklistManagerTest.java
@@ -0,0 +1,236 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.contentprotection;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyZeroInteractions;
+import static org.mockito.Mockito.when;
+
+import android.annotation.NonNull;
+import android.content.pm.PackageInfo;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
+import com.google.common.collect.ImmutableList;
+
+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;
+import org.mockito.junit.MockitoRule;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Test for {@link ContentProtectionBlocklistManager}.
+ *
+ * <p>Run with: {@code atest
+ * FrameworksServicesTests:
+ * com.android.server.contentprotection.ContentProtectionBlocklistManagerTest}
+ */
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class ContentProtectionBlocklistManagerTest {
+
+    private static final String FIRST_PACKAGE_NAME = "com.test.first.package.name";
+
+    private static final String SECOND_PACKAGE_NAME = "com.test.second.package.name";
+
+    private static final String UNLISTED_PACKAGE_NAME = "com.test.unlisted.package.name";
+
+    private static final String PACKAGE_NAME_BLOCKLIST_FILENAME =
+            "/product/etc/res/raw/content_protection/package_name_blocklist.txt";
+
+    private static final PackageInfo PACKAGE_INFO = new PackageInfo();
+
+    private static final List<String> LINES =
+            ImmutableList.of(FIRST_PACKAGE_NAME, SECOND_PACKAGE_NAME);
+
+    @Rule public final MockitoRule mMockitoRule = MockitoJUnit.rule();
+
+    @Mock private ContentProtectionPackageManager mMockContentProtectionPackageManager;
+
+    private final List<String> mReadRawFiles = new ArrayList<>();
+
+    private ContentProtectionBlocklistManager mContentProtectionBlocklistManager;
+
+    @Before
+    public void setup() {
+        mContentProtectionBlocklistManager = new TestContentProtectionBlocklistManager();
+    }
+
+    @Test
+    public void isAllowed_blocklistNotLoaded() {
+        boolean actual = mContentProtectionBlocklistManager.isAllowed(FIRST_PACKAGE_NAME);
+
+        assertThat(actual).isFalse();
+        assertThat(mReadRawFiles).isEmpty();
+        verifyZeroInteractions(mMockContentProtectionPackageManager);
+    }
+
+    @Test
+    public void isAllowed_inBlocklist() {
+        mContentProtectionBlocklistManager.updateBlocklist(LINES.size());
+
+        boolean actual = mContentProtectionBlocklistManager.isAllowed(FIRST_PACKAGE_NAME);
+
+        assertThat(actual).isFalse();
+        verifyZeroInteractions(mMockContentProtectionPackageManager);
+    }
+
+    @Test
+    public void isAllowed_packageInfoNotFound() {
+        mContentProtectionBlocklistManager.updateBlocklist(LINES.size());
+        when(mMockContentProtectionPackageManager.getPackageInfo(UNLISTED_PACKAGE_NAME))
+                .thenReturn(null);
+
+        boolean actual = mContentProtectionBlocklistManager.isAllowed(UNLISTED_PACKAGE_NAME);
+
+        assertThat(actual).isFalse();
+        verify(mMockContentProtectionPackageManager, never())
+                .hasRequestedInternetPermissions(any());
+        verify(mMockContentProtectionPackageManager, never()).isSystemApp(any());
+        verify(mMockContentProtectionPackageManager, never()).isUpdatedSystemApp(any());
+    }
+
+    @Test
+    public void isAllowed_notRequestedInternet() {
+        mContentProtectionBlocklistManager.updateBlocklist(LINES.size());
+        when(mMockContentProtectionPackageManager.getPackageInfo(UNLISTED_PACKAGE_NAME))
+                .thenReturn(PACKAGE_INFO);
+        when(mMockContentProtectionPackageManager.hasRequestedInternetPermissions(PACKAGE_INFO))
+                .thenReturn(false);
+
+        boolean actual = mContentProtectionBlocklistManager.isAllowed(UNLISTED_PACKAGE_NAME);
+
+        assertThat(actual).isFalse();
+        verify(mMockContentProtectionPackageManager, never()).isSystemApp(any());
+        verify(mMockContentProtectionPackageManager, never()).isUpdatedSystemApp(any());
+    }
+
+    @Test
+    public void isAllowed_systemApp() {
+        mContentProtectionBlocklistManager.updateBlocklist(LINES.size());
+        when(mMockContentProtectionPackageManager.getPackageInfo(UNLISTED_PACKAGE_NAME))
+                .thenReturn(PACKAGE_INFO);
+        when(mMockContentProtectionPackageManager.hasRequestedInternetPermissions(PACKAGE_INFO))
+                .thenReturn(true);
+        when(mMockContentProtectionPackageManager.isSystemApp(PACKAGE_INFO)).thenReturn(true);
+
+        boolean actual = mContentProtectionBlocklistManager.isAllowed(UNLISTED_PACKAGE_NAME);
+
+        assertThat(actual).isFalse();
+        verify(mMockContentProtectionPackageManager, never()).isUpdatedSystemApp(any());
+    }
+
+    @Test
+    public void isAllowed_updatedSystemApp() {
+        mContentProtectionBlocklistManager.updateBlocklist(LINES.size());
+        when(mMockContentProtectionPackageManager.getPackageInfo(UNLISTED_PACKAGE_NAME))
+                .thenReturn(PACKAGE_INFO);
+        when(mMockContentProtectionPackageManager.hasRequestedInternetPermissions(PACKAGE_INFO))
+                .thenReturn(true);
+        when(mMockContentProtectionPackageManager.isSystemApp(PACKAGE_INFO)).thenReturn(true);
+        when(mMockContentProtectionPackageManager.isUpdatedSystemApp(PACKAGE_INFO))
+                .thenReturn(true);
+
+        boolean actual = mContentProtectionBlocklistManager.isAllowed(UNLISTED_PACKAGE_NAME);
+
+        assertThat(actual).isFalse();
+    }
+
+    @Test
+    public void isAllowed_allowed() {
+        mContentProtectionBlocklistManager.updateBlocklist(LINES.size());
+        when(mMockContentProtectionPackageManager.getPackageInfo(UNLISTED_PACKAGE_NAME))
+                .thenReturn(PACKAGE_INFO);
+        when(mMockContentProtectionPackageManager.hasRequestedInternetPermissions(PACKAGE_INFO))
+                .thenReturn(true);
+        when(mMockContentProtectionPackageManager.isSystemApp(PACKAGE_INFO)).thenReturn(false);
+        when(mMockContentProtectionPackageManager.isUpdatedSystemApp(PACKAGE_INFO))
+                .thenReturn(false);
+
+        boolean actual = mContentProtectionBlocklistManager.isAllowed(UNLISTED_PACKAGE_NAME);
+
+        assertThat(actual).isTrue();
+    }
+
+    @Test
+    public void updateBlocklist_negativeSize() {
+        mContentProtectionBlocklistManager.updateBlocklist(/* blocklistSize= */ -1);
+        assertThat(mReadRawFiles).isEmpty();
+
+        mContentProtectionBlocklistManager.isAllowed(FIRST_PACKAGE_NAME);
+        verify(mMockContentProtectionPackageManager).getPackageInfo(FIRST_PACKAGE_NAME);
+    }
+
+    @Test
+    public void updateBlocklist_zeroSize() {
+        mContentProtectionBlocklistManager.updateBlocklist(/* blocklistSize= */ 0);
+        assertThat(mReadRawFiles).isEmpty();
+
+        mContentProtectionBlocklistManager.isAllowed(FIRST_PACKAGE_NAME);
+        verify(mMockContentProtectionPackageManager).getPackageInfo(FIRST_PACKAGE_NAME);
+    }
+
+    @Test
+    public void updateBlocklist_positiveSize_belowTotal() {
+        mContentProtectionBlocklistManager.updateBlocklist(/* blocklistSize= */ 1);
+        assertThat(mReadRawFiles).containsExactly(PACKAGE_NAME_BLOCKLIST_FILENAME);
+
+        mContentProtectionBlocklistManager.isAllowed(FIRST_PACKAGE_NAME);
+        mContentProtectionBlocklistManager.isAllowed(SECOND_PACKAGE_NAME);
+
+        verify(mMockContentProtectionPackageManager, never()).getPackageInfo(FIRST_PACKAGE_NAME);
+        verify(mMockContentProtectionPackageManager).getPackageInfo(SECOND_PACKAGE_NAME);
+    }
+
+    @Test
+    public void updateBlocklist_positiveSize_aboveTotal() {
+        mContentProtectionBlocklistManager.updateBlocklist(LINES.size() + 1);
+        assertThat(mReadRawFiles).containsExactly(PACKAGE_NAME_BLOCKLIST_FILENAME);
+
+        mContentProtectionBlocklistManager.isAllowed(FIRST_PACKAGE_NAME);
+        mContentProtectionBlocklistManager.isAllowed(SECOND_PACKAGE_NAME);
+
+        verify(mMockContentProtectionPackageManager, never()).getPackageInfo(FIRST_PACKAGE_NAME);
+        verify(mMockContentProtectionPackageManager, never()).getPackageInfo(SECOND_PACKAGE_NAME);
+    }
+
+    private final class TestContentProtectionBlocklistManager
+            extends ContentProtectionBlocklistManager {
+
+        TestContentProtectionBlocklistManager() {
+            super(mMockContentProtectionPackageManager);
+        }
+
+        @Override
+        protected List<String> readLinesFromRawFile(@NonNull String filename) {
+            mReadRawFiles.add(filename);
+            return LINES;
+        }
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/LockscreenFrpTest.java b/services/tests/servicestests/src/com/android/server/locksettings/LockscreenFrpTest.java
index 2b49b8a..a242cde 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/LockscreenFrpTest.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/LockscreenFrpTest.java
@@ -23,6 +23,8 @@
 import static com.android.internal.widget.LockPatternUtils.USER_FRP;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
 
 import android.app.PropertyInvalidatedCache;
 import android.app.admin.DevicePolicyManager;
@@ -38,8 +40,9 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
+import java.nio.ByteBuffer;
 
-/** Test setting a lockscreen credential and then verify it under USER_FRP */
+/** Tests that involve the Factory Reset Protection (FRP) credential. */
 @SmallTest
 @Presubmit
 @RunWith(AndroidJUnit4.class)
@@ -148,4 +151,68 @@
                 mService.verifyCredential(newPin("1234"), USER_FRP, 0 /* flags */)
                         .getResponseCode());
     }
+
+    // The FRP block that gets written by the current version of Android must still be accepted by
+    // old versions of Android.  This test tries to detect non-forward-compatible changes in
+    // PasswordData#toBytes(), which would break that.
+    @Test
+    public void testFrpBlock_isForwardsCompatible() {
+        mService.setLockCredential(newPin("1234"), nonePassword(), PRIMARY_USER_ID);
+        PersistentData data = mStorage.readPersistentDataBlock();
+        ByteBuffer buffer = ByteBuffer.wrap(data.payload);
+
+        final int credentialType = buffer.getInt();
+        assertEquals(CREDENTIAL_TYPE_PIN, credentialType);
+
+        final byte scryptLogN = buffer.get();
+        assertTrue(scryptLogN >= 0);
+
+        final byte scryptLogR = buffer.get();
+        assertTrue(scryptLogR >= 0);
+
+        final byte scryptLogP = buffer.get();
+        assertTrue(scryptLogP >= 0);
+
+        final int saltLength = buffer.getInt();
+        assertTrue(saltLength > 0);
+        final byte[] salt = new byte[saltLength];
+        buffer.get(salt);
+
+        final int passwordHandleLength = buffer.getInt();
+        assertTrue(passwordHandleLength > 0);
+        final byte[] passwordHandle = new byte[passwordHandleLength];
+        buffer.get(passwordHandle);
+    }
+
+    @Test
+    public void testFrpBlock_inBadAndroid14FormatIsAutomaticallyFixed() {
+        mService.setLockCredential(newPin("1234"), nonePassword(), PRIMARY_USER_ID);
+
+        // Write a "bad" FRP block with PasswordData beginning with the bytes [0, 2].
+        byte[] badPasswordData = new byte[] {
+                0, 2, /* version 2 */
+                0, 3, /* CREDENTIAL_TYPE_PIN */
+                11, /* scryptLogN */
+                22, /* scryptLogR */
+                33, /* scryptLogP */
+                0, 0, 0, 5, /* salt.length */
+                1, 2, -1, -2, 55, /* salt */
+                0, 0, 0, 6, /* passwordHandle.length */
+                2, 3, -2, -3, 44, 1, /* passwordHandle */
+                0, 0, 0, 6, /* pinLength */
+        };
+        mStorage.writePersistentDataBlock(PersistentData.TYPE_SP_GATEKEEPER, PRIMARY_USER_ID, 0,
+                badPasswordData);
+
+        // Execute the code that should fix the FRP block.
+        assertFalse(mStorage.getBoolean("migrated_frp2", false, 0));
+        mService.migrateOldDataAfterSystemReady();
+        assertTrue(mStorage.getBoolean("migrated_frp2", false, 0));
+
+        // Verify that the FRP block has been fixed.
+        PersistentData data = mStorage.readPersistentDataBlock();
+        assertEquals(PersistentData.TYPE_SP_GATEKEEPER, data.type);
+        ByteBuffer buffer = ByteBuffer.wrap(data.payload);
+        assertEquals(CREDENTIAL_TYPE_PIN, buffer.getInt());
+    }
 }
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/SyntheticPasswordTests.java b/services/tests/servicestests/src/com/android/server/locksettings/SyntheticPasswordTests.java
index 067feae..ce0347d 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/SyntheticPasswordTests.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/SyntheticPasswordTests.java
@@ -638,6 +638,7 @@
                 2, 3, -2, -3, 44, 1, /* passwordHandle */
                 0, 0, 0, 6, /* pinLength */
         };
+        assertFalse(PasswordData.isBadFormatFromAndroid14Beta(serialized));
         PasswordData deserialized = PasswordData.fromBytes(serialized);
 
         assertEquals(11, deserialized.scryptLogN);
@@ -690,6 +691,7 @@
                 2, 3, -2, -3, 44, 1, /* passwordHandle */
                 0, 0, 0, 6, /* pinLength */
         };
+        assertTrue(PasswordData.isBadFormatFromAndroid14Beta(serialized));
         PasswordData deserialized = PasswordData.fromBytes(serialized);
 
         assertEquals(11, deserialized.scryptLogN);
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 27677e1..0e627b2 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationVisitUrisTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationVisitUrisTest.java
@@ -88,7 +88,6 @@
     private static final Multimap<Class<?>, String> KNOWN_BAD =
             ImmutableMultimap.<Class<?>, String>builder()
                     .put(Notification.Builder.class, "setPublicVersion") // b/276294099
-                    .putAll(RemoteViews.class, "addView", "addStableView") // b/277740082
                     .put(RemoteViews.class, "setIcon") // b/281018094
                     .put(Notification.WearableExtender.class, "addAction") // TODO: b/281044385
                     .put(Person.Builder.class, "setUri") // TODO: b/281044385
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java
index 8b8744b..d179338 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java
@@ -944,7 +944,7 @@
                 anyInt(), anyInt()));
         doReturn(BackgroundStartPrivileges.allowBackgroundActivityStarts(null)).when(
                 () -> PendingIntentRecord.getBackgroundStartPrivilegesAllowedByCaller(
-                anyObject(), anyInt()));
+                anyObject(), anyInt(), anyObject()));
         runAndVerifyBackgroundActivityStartsSubtest(
                 "allowed_notAborted", false,
                 UNIMPORTANT_UID, false, PROCESS_STATE_BOUND_TOP,
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTests.java
index 5ec3604..a422c68 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTests.java
@@ -178,6 +178,44 @@
                 dimmingNonImTarget, imeNonDrawNavBar, NAV_BAR_BOTTOM));
     }
 
+    @Test
+    public void testChooseNavigationBackgroundWindow() {
+        final WindowState drawBarWin = createOpaqueFullscreen(false);
+        final WindowState nonDrawBarWin = createDimmingDialogWindow(true);
+
+        final WindowState visibleIme = createInputMethodWindow(true, true, false);
+        final WindowState invisibleIme = createInputMethodWindow(false, true, false);
+        final WindowState nonDrawBarIme = createInputMethodWindow(true, false, false);
+
+        assertEquals(drawBarWin, DisplayPolicy.chooseNavigationBackgroundWindow(
+                drawBarWin, null, NAV_BAR_BOTTOM));
+        assertNull(DisplayPolicy.chooseNavigationBackgroundWindow(
+                null, null, NAV_BAR_BOTTOM));
+        assertNull(DisplayPolicy.chooseNavigationBackgroundWindow(
+                nonDrawBarWin, null, NAV_BAR_BOTTOM));
+
+        assertEquals(visibleIme, DisplayPolicy.chooseNavigationBackgroundWindow(
+                drawBarWin, visibleIme, NAV_BAR_BOTTOM));
+        assertEquals(visibleIme, DisplayPolicy.chooseNavigationBackgroundWindow(
+                null, visibleIme, NAV_BAR_BOTTOM));
+        assertEquals(visibleIme, DisplayPolicy.chooseNavigationBackgroundWindow(
+                nonDrawBarWin, visibleIme, NAV_BAR_BOTTOM));
+
+        assertEquals(drawBarWin, DisplayPolicy.chooseNavigationBackgroundWindow(
+                drawBarWin, invisibleIme, NAV_BAR_BOTTOM));
+        assertNull(DisplayPolicy.chooseNavigationBackgroundWindow(
+                null, invisibleIme, NAV_BAR_BOTTOM));
+        assertNull(DisplayPolicy.chooseNavigationBackgroundWindow(
+                nonDrawBarWin, invisibleIme, NAV_BAR_BOTTOM));
+
+        assertEquals(drawBarWin, DisplayPolicy.chooseNavigationBackgroundWindow(
+                drawBarWin, nonDrawBarIme, NAV_BAR_BOTTOM));
+        assertNull(DisplayPolicy.chooseNavigationBackgroundWindow(
+                null, nonDrawBarIme, NAV_BAR_BOTTOM));
+        assertNull(DisplayPolicy.chooseNavigationBackgroundWindow(
+                nonDrawBarWin, nonDrawBarIme, NAV_BAR_BOTTOM));
+    }
+
     @SetupWindows(addWindows = W_NAVIGATION_BAR)
     @Test
     public void testUpdateLightNavigationBarLw() {
diff --git a/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java
index 7d507e9..34a13bf 100644
--- a/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java
@@ -38,6 +38,7 @@
 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
 import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
 import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
+import static android.view.InsetsSource.FLAG_INSETS_ROUNDED_CORNER;
 import static android.view.WindowManager.PROPERTY_CAMERA_COMPAT_ALLOW_FORCE_ROTATION;
 import static android.view.WindowManager.PROPERTY_CAMERA_COMPAT_ALLOW_REFRESH;
 import static android.view.WindowManager.PROPERTY_CAMERA_COMPAT_ENABLE_REFRESH_VIA_PAUSE;
@@ -461,7 +462,7 @@
     public void testGetCropBoundsIfNeeded_handleCropForTransparentActivityBasedOnOpaqueBounds() {
         final InsetsSource taskbar = new InsetsSource(/*id=*/ 0,
                  WindowInsets.Type.navigationBars());
-        taskbar.setInsetsRoundedCornerFrame(true);
+        taskbar.setFlags(FLAG_INSETS_ROUNDED_CORNER, FLAG_INSETS_ROUNDED_CORNER);
         final WindowState mainWindow = mockForGetCropBoundsAndRoundedCorners(taskbar);
         final Rect opaqueBounds = new Rect(0, 0, 500, 300);
         doReturn(opaqueBounds).when(mActivity).getBounds();
@@ -505,7 +506,7 @@
     public void testGetCropBoundsIfNeeded_appliesCrop() {
         final InsetsSource taskbar = new InsetsSource(/*id=*/ 0,
                 WindowInsets.Type.navigationBars());
-        taskbar.setInsetsRoundedCornerFrame(true);
+        taskbar.setFlags(FLAG_INSETS_ROUNDED_CORNER, FLAG_INSETS_ROUNDED_CORNER);
         final WindowState mainWindow = mockForGetCropBoundsAndRoundedCorners(taskbar);
 
         // Apply crop if taskbar is expanded
@@ -528,7 +529,7 @@
     public void testGetCropBoundsIfNeeded_appliesCropWithSizeCompatScaling() {
         final InsetsSource taskbar = new InsetsSource(/*id=*/ 0,
                 WindowInsets.Type.navigationBars());
-        taskbar.setInsetsRoundedCornerFrame(true);
+        taskbar.setFlags(FLAG_INSETS_ROUNDED_CORNER, FLAG_INSETS_ROUNDED_CORNER);
         final WindowState mainWindow = mockForGetCropBoundsAndRoundedCorners(taskbar);
         final float scaling = 2.0f;
 
diff --git a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
index 2dd34eb..d91be16 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
@@ -30,6 +30,7 @@
 import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
 import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
 import static android.provider.DeviceConfig.NAMESPACE_CONSTRAIN_DISPLAY_APIS;
+import static android.view.InsetsSource.FLAG_INSETS_ROUNDED_CORNER;
 import static android.view.Surface.ROTATION_0;
 import static android.view.Surface.ROTATION_180;
 import static android.view.Surface.ROTATION_270;
@@ -3483,7 +3484,7 @@
 
         final InsetsSource navSource = new InsetsSource(
                 InsetsSource.createId(null, 0, navigationBars()), navigationBars());
-        navSource.setInsetsRoundedCornerFrame(true);
+        navSource.setFlags(FLAG_INSETS_ROUNDED_CORNER, FLAG_INSETS_ROUNDED_CORNER);
         navSource.setFrame(new Rect(0, screenHeight - taskbarHeight, screenWidth, screenHeight));
 
         mActivity.mWmService.mLetterboxConfiguration.setLetterboxActivityCornersRadius(15);
@@ -3531,7 +3532,7 @@
 
         final InsetsSource navSource = new InsetsSource(
                 InsetsSource.createId(null, 0, navigationBars()), navigationBars());
-        navSource.setInsetsRoundedCornerFrame(true);
+        navSource.setFlags(FLAG_INSETS_ROUNDED_CORNER, FLAG_INSETS_ROUNDED_CORNER);
         // Immersive activity has transient navbar
         navSource.setVisible(!immersive);
         navSource.setFrame(new Rect(0, screenHeight - taskbarHeight, screenWidth, screenHeight));
diff --git a/tests/FlickerTests/Android.bp b/tests/FlickerTests/Android.bp
index 4ba538e..2f96eec 100644
--- a/tests/FlickerTests/Android.bp
+++ b/tests/FlickerTests/Android.bp
@@ -23,14 +23,49 @@
     default_applicable_licenses: ["frameworks_base_license"],
 }
 
-android_test {
-    name: "FlickerTests",
+filegroup {
+    name: "FlickerTestsBase-src",
+    srcs: ["src/com/android/server/wm/flicker/*.kt"],
+}
+
+filegroup {
+    name: "FlickerTestsAppClose-src",
+    srcs: ["src/**/close/*.kt"],
+}
+
+filegroup {
+    name: "FlickerTestsActivityEmbedding-src",
     srcs: [
-        "src/**/*.java",
-        "src/**/*.kt",
+        "src/**/activityembedding/*.kt",
+        "src/**/activityembedding/close/*.kt",
+        "src/**/activityembedding/rotation/*.kt",
     ],
-    manifest: "AndroidManifest.xml",
-    test_config: "AndroidTest.xml",
+}
+
+filegroup {
+    name: "FlickerTestsIme-src",
+    srcs: ["src/**/ime/*.kt"],
+}
+
+filegroup {
+    name: "FlickerTestsAppLaunch-src",
+    srcs: ["src/**/launch/*.kt"],
+}
+
+filegroup {
+    name: "FlickerTestsQuickswitch-src",
+    srcs: ["src/**/quickswitch/*.kt"],
+}
+
+filegroup {
+    name: "FlickerTestsRotation-src",
+    srcs: ["src/**/rotation/*.kt"],
+}
+
+java_defaults {
+    name: "FlickerTestsDefault",
+    manifest: "manifests/AndroidManifest.xml",
+    test_config_template: "AndroidTestTemplate.xml",
     platform_apis: true,
     certificate: "platform",
     optimize: {
@@ -42,19 +77,100 @@
         "androidx.test.ext.junit",
         "flickertestapplib",
         "flickerlib",
-        "flickerlib-apphelpers",
         "flickerlib-helpers",
-        "truth-prebuilt",
-        "launcher-helper-lib",
-        "launcher-aosp-tapl",
         "platform-test-annotations",
-        "wm-flicker-window-extensions",
+        "wm-flicker-common-app-helpers",
     ],
     data: [
         ":FlickerTestApp",
     ],
 }
 
+android_test {
+    name: "FlickerTestsOther",
+    defaults: ["FlickerTestsDefault"],
+    additional_manifests: ["manifests/AndroidManifestOther.xml"],
+    package_name: "com.android.server.wm.flicker",
+    instrumentation_target_package: "com.android.server.wm.flicker",
+    srcs: [
+        "src/**/*.java",
+        "src/**/*.kt",
+    ],
+    exclude_srcs: [
+        ":FlickerTestsAppClose-src",
+        ":FlickerTestsIme-src",
+        ":FlickerTestsAppLaunch-src",
+        ":FlickerTestsQuickswitch-src",
+        ":FlickerTestsRotation-src",
+    ],
+}
+
+android_test {
+    name: "FlickerTestsAppClose",
+    defaults: ["FlickerTestsDefault"],
+    additional_manifests: ["manifests/AndroidManifestAppClose.xml"],
+    package_name: "com.android.server.wm.flicker.close",
+    instrumentation_target_package: "com.android.server.wm.flicker.close",
+    srcs: [
+        ":FlickerTestsBase-src",
+        ":FlickerTestsAppClose-src",
+    ],
+    exclude_srcs: [
+        ":FlickerTestsActivityEmbedding-src",
+    ],
+}
+
+android_test {
+    name: "FlickerTestsIme",
+    defaults: ["FlickerTestsDefault"],
+    additional_manifests: ["manifests/AndroidManifestIme.xml"],
+    package_name: "com.android.server.wm.flicker.ime",
+    instrumentation_target_package: "com.android.server.wm.flicker.ime",
+    srcs: [
+        ":FlickerTestsBase-src",
+        ":FlickerTestsIme-src",
+    ],
+}
+
+android_test {
+    name: "FlickerTestsAppLaunch",
+    defaults: ["FlickerTestsDefault"],
+    additional_manifests: ["manifests/AndroidManifestAppLaunch.xml"],
+    package_name: "com.android.server.wm.flicker.launch",
+    instrumentation_target_package: "com.android.server.wm.flicker.launch",
+    srcs: [
+        ":FlickerTestsBase-src",
+        ":FlickerTestsAppLaunch-src",
+    ],
+}
+
+android_test {
+    name: "FlickerTestsQuickswitch",
+    defaults: ["FlickerTestsDefault"],
+    additional_manifests: ["manifests/AndroidManifestQuickswitch.xml"],
+    package_name: "com.android.server.wm.flicker.rotation",
+    instrumentation_target_package: "com.android.server.wm.flicker.rotation",
+    srcs: [
+        ":FlickerTestsBase-src",
+        ":FlickerTestsQuickswitch-src",
+    ],
+}
+
+android_test {
+    name: "FlickerTestsRotation",
+    defaults: ["FlickerTestsDefault"],
+    additional_manifests: ["manifests/AndroidManifestRotation.xml"],
+    package_name: "com.android.server.wm.flicker.rotation",
+    instrumentation_target_package: "com.android.server.wm.flicker.rotation",
+    srcs: [
+        ":FlickerTestsBase-src",
+        ":FlickerTestsRotation-src",
+    ],
+    exclude_srcs: [
+        ":FlickerTestsActivityEmbedding-src",
+    ],
+}
+
 java_library {
     name: "wm-flicker-common-assertions",
     platform_apis: true,
diff --git a/tests/FlickerTests/AndroidTest.xml b/tests/FlickerTests/AndroidTestTemplate.xml
similarity index 93%
rename from tests/FlickerTests/AndroidTest.xml
rename to tests/FlickerTests/AndroidTestTemplate.xml
index 32ff243..1176828 100644
--- a/tests/FlickerTests/AndroidTest.xml
+++ b/tests/FlickerTests/AndroidTestTemplate.xml
@@ -2,7 +2,7 @@
 <!--
  * Copyright 2018 Google Inc. All Rights Reserved.
  -->
-<configuration description="Runs WindowManager Flicker Tests">
+<configuration description="Runs WindowManager {MODULE}">
     <option name="test-tag" value="FlickerTests" />
     <target_preparer class="com.android.tradefed.targetprep.DeviceSetup">
         <!-- keeps the screen on during tests -->
@@ -36,11 +36,11 @@
     </target_preparer>
     <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
         <option name="cleanup-apks" value="true"/>
-        <option name="test-file-name" value="FlickerTests.apk"/>
+        <option name="test-file-name" value="{MODULE}.apk"/>
         <option name="test-file-name" value="FlickerTestApp.apk" />
     </target_preparer>
     <test class="com.android.tradefed.testtype.AndroidJUnitTest">
-        <option name="package" value="com.android.server.wm.flicker"/>
+        <option name="package" value="{PACKAGE}"/>
         <option name="shell-timeout" value="6600s" />
         <option name="test-timeout" value="6600s" />
         <option name="hidden-api-checks" value="false" />
diff --git a/tests/FlickerTests/AndroidManifest.xml b/tests/FlickerTests/manifests/AndroidManifest.xml
similarity index 70%
rename from tests/FlickerTests/AndroidManifest.xml
rename to tests/FlickerTests/manifests/AndroidManifest.xml
index 462f91b..5b00310 100644
--- a/tests/FlickerTests/AndroidManifest.xml
+++ b/tests/FlickerTests/manifests/AndroidManifest.xml
@@ -1,18 +1,19 @@
 <?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2018 The Android Open Source Project
-
-     Licensed under the Apache License, Version 2.0 (the "License");
-     you may not use this file except in compliance with the License.
-     You may obtain a copy of the License at
-
-          http://www.apache.org/licenses/LICENSE-2.0
-
-     Unless required by applicable law or agreed to in writing, software
-     distributed under the License is distributed on an "AS IS" BASIS,
-     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-     See the License for the specific language governing permissions and
-     limitations under the License.
--->
+<!--
+  ~ Copyright (C) 2023 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT 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">
@@ -48,8 +49,8 @@
         <uses-library android:name="androidx.window.extensions" android:required="false"/>
     </application>
 
-    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
-                     android:targetPackage="com.android.server.wm.flicker"
-                     android:label="WindowManager Flicker Tests">
-    </instrumentation>
+    <!--<instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+                     android:targetPackage="{{package}}"
+                     android:label="WindowManager Flicker Tests {MODULE}">
+    </instrumentation>-->
 </manifest>
diff --git a/tests/FlickerTests/manifests/AndroidManifestAppClose.xml b/tests/FlickerTests/manifests/AndroidManifestAppClose.xml
new file mode 100644
index 0000000..4cdcb90
--- /dev/null
+++ b/tests/FlickerTests/manifests/AndroidManifestAppClose.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.close">
+
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+                     android:targetPackage="com.android.server.wm.flicker.close"
+                     android:label="WindowManager Flicker Tests">
+    </instrumentation>
+</manifest>
diff --git a/tests/FlickerTests/manifests/AndroidManifestAppLaunch.xml b/tests/FlickerTests/manifests/AndroidManifestAppLaunch.xml
new file mode 100644
index 0000000..659a745
--- /dev/null
+++ b/tests/FlickerTests/manifests/AndroidManifestAppLaunch.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.launch">
+
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+                     android:targetPackage="com.android.server.wm.flicker.launch"
+                     android:label="WindowManager Flicker Tests">
+    </instrumentation>
+</manifest>
diff --git a/tests/FlickerTests/manifests/AndroidManifestIme.xml b/tests/FlickerTests/manifests/AndroidManifestIme.xml
new file mode 100644
index 0000000..abd03af
--- /dev/null
+++ b/tests/FlickerTests/manifests/AndroidManifestIme.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.ime">
+
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+                     android:targetPackage="com.android.server.wm.flicker.ime"
+                     android:label="WindowManager Flicker Tests">
+    </instrumentation>
+</manifest>
diff --git a/tests/FlickerTests/manifests/AndroidManifestOther.xml b/tests/FlickerTests/manifests/AndroidManifestOther.xml
new file mode 100644
index 0000000..47749b8
--- /dev/null
+++ b/tests/FlickerTests/manifests/AndroidManifestOther.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">
+
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+                     android:targetPackage="com.android.server.wm.flicker"
+                     android:label="WindowManager Flicker Tests">
+    </instrumentation>
+</manifest>
diff --git a/tests/FlickerTests/manifests/AndroidManifestQuickswitch.xml b/tests/FlickerTests/manifests/AndroidManifestQuickswitch.xml
new file mode 100644
index 0000000..203035d
--- /dev/null
+++ b/tests/FlickerTests/manifests/AndroidManifestQuickswitch.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.quickswitch">
+
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+                     android:targetPackage="com.android.server.wm.flicker.quickswitch"
+                     android:label="WindowManager Flicker Tests">
+    </instrumentation>
+</manifest>
diff --git a/tests/FlickerTests/manifests/AndroidManifestRotation.xml b/tests/FlickerTests/manifests/AndroidManifestRotation.xml
new file mode 100644
index 0000000..2852cf2
--- /dev/null
+++ b/tests/FlickerTests/manifests/AndroidManifestRotation.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.rotation">
+
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+                     android:targetPackage="com.android.server.wm.flicker.rotation"
+                     android:label="WindowManager Flicker Tests">
+    </instrumentation>
+</manifest>