Merge "[Central Surfaces] Remove unused methods & variables from interface." into udc-qpr-dev
diff --git a/core/java/android/app/WallpaperInfo.java b/core/java/android/app/WallpaperInfo.java
index 99d4064..b29e73a 100644
--- a/core/java/android/app/WallpaperInfo.java
+++ b/core/java/android/app/WallpaperInfo.java
@@ -292,12 +292,12 @@
             packageName = mService.serviceInfo.packageName;
             applicationInfo = mService.serviceInfo.applicationInfo;
         }
-        String contextUriString = pm.getText(
-                packageName, mContextUriResource, applicationInfo).toString();
-        if (contextUriString == null) {
+        CharSequence contextUriCharSequence = pm.getText(
+                packageName, mContextUriResource, applicationInfo);
+        if (contextUriCharSequence == null) {
             return null;
         }
-        return Uri.parse(contextUriString);
+        return Uri.parse(contextUriCharSequence.toString());
     }
 
     /**
diff --git a/core/java/android/content/ContentCaptureOptions.java b/core/java/android/content/ContentCaptureOptions.java
index 856bde8..36e0529 100644
--- a/core/java/android/content/ContentCaptureOptions.java
+++ b/core/java/android/content/ContentCaptureOptions.java
@@ -76,6 +76,20 @@
     public final boolean disableFlushForViewTreeAppearing;
 
     /**
+     * Is the content capture receiver enabled.
+     *
+     * @hide
+     */
+    public final boolean enableReceiver;
+
+    /**
+     * Options for the content protection flow.
+     *
+     * @hide
+     */
+    @NonNull public final ContentProtectionOptions contentProtectionOptions;
+
+    /**
      * List of activities explicitly allowlisted for content capture (or {@code null} if allowlisted
      * for all acitivites in the package).
      */
@@ -94,52 +108,99 @@
      * for contexts belonging to the content capture service app.
      */
     public ContentCaptureOptions(int loggingLevel) {
-        this(/* lite= */ true, loggingLevel, /* maxBufferSize= */ 0,
-                /* idleFlushingFrequencyMs= */ 0, /* textChangeFlushingFrequencyMs= */ 0,
-                /* logHistorySize= */ 0, /* disableFlushForViewTreeAppearing= */ false,
+        this(
+                /* lite= */ true,
+                loggingLevel,
+                /* maxBufferSize= */ 0,
+                /* idleFlushingFrequencyMs= */ 0,
+                /* textChangeFlushingFrequencyMs= */ 0,
+                /* logHistorySize= */ 0,
+                /* disableFlushForViewTreeAppearing= */ false,
+                /* enableReceiver= */ false,
+                new ContentProtectionOptions(
+                        /* enableReceiver= */ false,
+                        /* bufferSize= */ 0),
                 /* whitelistedComponents= */ null);
     }
 
-    /**
-     * Default constructor.
-     */
-    public ContentCaptureOptions(int loggingLevel, int maxBufferSize, int idleFlushingFrequencyMs,
-            int textChangeFlushingFrequencyMs, int logHistorySize,
-            @SuppressLint({"ConcreteCollection", "NullableCollection"})
-            @Nullable ArraySet<ComponentName> whitelistedComponents) {
-        this(/* lite= */ false, loggingLevel, maxBufferSize, idleFlushingFrequencyMs,
-                textChangeFlushingFrequencyMs, logHistorySize,
+    /** Default constructor. */
+    public ContentCaptureOptions(
+            int loggingLevel,
+            int maxBufferSize,
+            int idleFlushingFrequencyMs,
+            int textChangeFlushingFrequencyMs,
+            int logHistorySize,
+            @SuppressLint({"ConcreteCollection", "NullableCollection"}) @Nullable
+                    ArraySet<ComponentName> whitelistedComponents) {
+        this(
+                /* lite= */ false,
+                loggingLevel,
+                maxBufferSize,
+                idleFlushingFrequencyMs,
+                textChangeFlushingFrequencyMs,
+                logHistorySize,
                 ContentCaptureManager.DEFAULT_DISABLE_FLUSH_FOR_VIEW_TREE_APPEARING,
+                ContentCaptureManager.DEFAULT_ENABLE_CONTENT_CAPTURE_RECEIVER,
+                new ContentProtectionOptions(
+                        ContentCaptureManager.DEFAULT_ENABLE_CONTENT_PROTECTION_RECEIVER,
+                        ContentCaptureManager.DEFAULT_CONTENT_PROTECTION_BUFFER_SIZE),
                 whitelistedComponents);
     }
 
     /** @hide */
-    public ContentCaptureOptions(int loggingLevel, int maxBufferSize, int idleFlushingFrequencyMs,
-            int textChangeFlushingFrequencyMs, int logHistorySize,
+    public ContentCaptureOptions(
+            int loggingLevel,
+            int maxBufferSize,
+            int idleFlushingFrequencyMs,
+            int textChangeFlushingFrequencyMs,
+            int logHistorySize,
             boolean disableFlushForViewTreeAppearing,
-            @SuppressLint({"ConcreteCollection", "NullableCollection"})
-            @Nullable ArraySet<ComponentName> whitelistedComponents) {
-        this(/* lite= */ false, loggingLevel, maxBufferSize, idleFlushingFrequencyMs,
-                textChangeFlushingFrequencyMs, logHistorySize, disableFlushForViewTreeAppearing,
+            boolean enableReceiver,
+            @NonNull ContentProtectionOptions contentProtectionOptions,
+            @SuppressLint({"ConcreteCollection", "NullableCollection"}) @Nullable
+                    ArraySet<ComponentName> whitelistedComponents) {
+        this(
+                /* lite= */ false,
+                loggingLevel,
+                maxBufferSize,
+                idleFlushingFrequencyMs,
+                textChangeFlushingFrequencyMs,
+                logHistorySize,
+                disableFlushForViewTreeAppearing,
+                enableReceiver,
+                contentProtectionOptions,
                 whitelistedComponents);
     }
 
     /** @hide */
     @VisibleForTesting
     public ContentCaptureOptions(@Nullable ArraySet<ComponentName> whitelistedComponents) {
-        this(ContentCaptureManager.LOGGING_LEVEL_VERBOSE,
+        this(
+                ContentCaptureManager.LOGGING_LEVEL_VERBOSE,
                 ContentCaptureManager.DEFAULT_MAX_BUFFER_SIZE,
                 ContentCaptureManager.DEFAULT_IDLE_FLUSHING_FREQUENCY_MS,
                 ContentCaptureManager.DEFAULT_TEXT_CHANGE_FLUSHING_FREQUENCY_MS,
                 ContentCaptureManager.DEFAULT_LOG_HISTORY_SIZE,
                 ContentCaptureManager.DEFAULT_DISABLE_FLUSH_FOR_VIEW_TREE_APPEARING,
+                ContentCaptureManager.DEFAULT_ENABLE_CONTENT_CAPTURE_RECEIVER,
+                new ContentProtectionOptions(
+                        ContentCaptureManager.DEFAULT_ENABLE_CONTENT_PROTECTION_RECEIVER,
+                        ContentCaptureManager.DEFAULT_CONTENT_PROTECTION_BUFFER_SIZE),
                 whitelistedComponents);
     }
 
-    private ContentCaptureOptions(boolean lite, int loggingLevel, int maxBufferSize,
-            int idleFlushingFrequencyMs, int textChangeFlushingFrequencyMs, int logHistorySize,
+    private ContentCaptureOptions(
+            boolean lite,
+            int loggingLevel,
+            int maxBufferSize,
+            int idleFlushingFrequencyMs,
+            int textChangeFlushingFrequencyMs,
+            int logHistorySize,
             boolean disableFlushForViewTreeAppearing,
-            @Nullable ArraySet<ComponentName> whitelistedComponents) {
+            boolean enableReceiver,
+            @NonNull ContentProtectionOptions contentProtectionOptions,
+            @SuppressLint({"ConcreteCollection", "NullableCollection"}) @Nullable
+                    ArraySet<ComponentName> whitelistedComponents) {
         this.lite = lite;
         this.loggingLevel = loggingLevel;
         this.maxBufferSize = maxBufferSize;
@@ -147,6 +208,8 @@
         this.textChangeFlushingFrequencyMs = textChangeFlushingFrequencyMs;
         this.logHistorySize = logHistorySize;
         this.disableFlushForViewTreeAppearing = disableFlushForViewTreeAppearing;
+        this.enableReceiver = enableReceiver;
+        this.contentProtectionOptions = contentProtectionOptions;
         this.whitelistedComponents = whitelistedComponents;
     }
 
@@ -191,12 +254,22 @@
             return "ContentCaptureOptions [loggingLevel=" + loggingLevel + " (lite)]";
         }
         final StringBuilder string = new StringBuilder("ContentCaptureOptions [");
-        string.append("loggingLevel=").append(loggingLevel)
-            .append(", maxBufferSize=").append(maxBufferSize)
-            .append(", idleFlushingFrequencyMs=").append(idleFlushingFrequencyMs)
-            .append(", textChangeFlushingFrequencyMs=").append(textChangeFlushingFrequencyMs)
-            .append(", logHistorySize=").append(logHistorySize)
-            .append(", disableFlushForViewTreeAppearing=").append(disableFlushForViewTreeAppearing);
+        string.append("loggingLevel=")
+                .append(loggingLevel)
+                .append(", maxBufferSize=")
+                .append(maxBufferSize)
+                .append(", idleFlushingFrequencyMs=")
+                .append(idleFlushingFrequencyMs)
+                .append(", textChangeFlushingFrequencyMs=")
+                .append(textChangeFlushingFrequencyMs)
+                .append(", logHistorySize=")
+                .append(logHistorySize)
+                .append(", disableFlushForViewTreeAppearing=")
+                .append(disableFlushForViewTreeAppearing)
+                .append(", enableReceiver=")
+                .append(enableReceiver)
+                .append(", contentProtectionOptions=")
+                .append(contentProtectionOptions);
         if (whitelistedComponents != null) {
             string.append(", whitelisted=").append(whitelistedComponents);
         }
@@ -210,11 +283,21 @@
             pw.print(", lite");
             return;
         }
-        pw.print(", bufferSize="); pw.print(maxBufferSize);
-        pw.print(", idle="); pw.print(idleFlushingFrequencyMs);
-        pw.print(", textIdle="); pw.print(textChangeFlushingFrequencyMs);
-        pw.print(", logSize="); pw.print(logHistorySize);
-        pw.print(", disableFlushForViewTreeAppearing="); pw.print(disableFlushForViewTreeAppearing);
+        pw.print(", bufferSize=");
+        pw.print(maxBufferSize);
+        pw.print(", idle=");
+        pw.print(idleFlushingFrequencyMs);
+        pw.print(", textIdle=");
+        pw.print(textChangeFlushingFrequencyMs);
+        pw.print(", logSize=");
+        pw.print(logHistorySize);
+        pw.print(", disableFlushForViewTreeAppearing=");
+        pw.print(disableFlushForViewTreeAppearing);
+        pw.print(", enableReceiver=");
+        pw.print(enableReceiver);
+        pw.print(", contentProtectionOptions=[");
+        contentProtectionOptions.dumpShort(pw);
+        pw.print("]");
         if (whitelistedComponents != null) {
             pw.print(", whitelisted="); pw.print(whitelistedComponents);
         }
@@ -236,6 +319,8 @@
         parcel.writeInt(textChangeFlushingFrequencyMs);
         parcel.writeInt(logHistorySize);
         parcel.writeBoolean(disableFlushForViewTreeAppearing);
+        parcel.writeBoolean(enableReceiver);
+        contentProtectionOptions.writeToParcel(parcel);
         parcel.writeArraySet(whitelistedComponents);
     }
 
@@ -254,12 +339,22 @@
                     final int textChangeFlushingFrequencyMs = parcel.readInt();
                     final int logHistorySize = parcel.readInt();
                     final boolean disableFlushForViewTreeAppearing = parcel.readBoolean();
+                    final boolean enableReceiver = parcel.readBoolean();
+                    final ContentProtectionOptions contentProtectionOptions =
+                            ContentProtectionOptions.createFromParcel(parcel);
                     @SuppressWarnings("unchecked")
                     final ArraySet<ComponentName> whitelistedComponents =
                             (ArraySet<ComponentName>) parcel.readArraySet(null);
-                    return new ContentCaptureOptions(loggingLevel, maxBufferSize,
-                            idleFlushingFrequencyMs, textChangeFlushingFrequencyMs, logHistorySize,
-                            disableFlushForViewTreeAppearing, whitelistedComponents);
+                    return new ContentCaptureOptions(
+                            loggingLevel,
+                            maxBufferSize,
+                            idleFlushingFrequencyMs,
+                            textChangeFlushingFrequencyMs,
+                            logHistorySize,
+                            disableFlushForViewTreeAppearing,
+                            enableReceiver,
+                            contentProtectionOptions,
+                            whitelistedComponents);
                 }
 
                 @Override
@@ -267,4 +362,62 @@
                     return new ContentCaptureOptions[size];
                 }
     };
+
+    /**
+     * Content protection options for a given package.
+     *
+     * <p>Does not implement {@code Parcelable} since it is an inner class without a matching AIDL.
+     *
+     * @hide
+     */
+    public static class ContentProtectionOptions {
+
+        /**
+         * Is the content protection receiver enabled.
+         *
+         * @hide
+         */
+        public final boolean enableReceiver;
+
+        /**
+         * Size of the in-memory ring buffer for the content protection flow.
+         *
+         * @hide
+         */
+        public final int bufferSize;
+
+        public ContentProtectionOptions(boolean enableReceiver, int bufferSize) {
+            this.enableReceiver = enableReceiver;
+            this.bufferSize = bufferSize;
+        }
+
+        @Override
+        public String toString() {
+            StringBuilder stringBuilder = new StringBuilder("ContentProtectionOptions [");
+            stringBuilder
+                    .append("enableReceiver=")
+                    .append(enableReceiver)
+                    .append(", bufferSize=")
+                    .append(bufferSize);
+            return stringBuilder.append(']').toString();
+        }
+
+        private void dumpShort(@NonNull PrintWriter pw) {
+            pw.print("enableReceiver=");
+            pw.print(enableReceiver);
+            pw.print(", bufferSize=");
+            pw.print(bufferSize);
+        }
+
+        private void writeToParcel(Parcel parcel) {
+            parcel.writeBoolean(enableReceiver);
+            parcel.writeInt(bufferSize);
+        }
+
+        private static ContentProtectionOptions createFromParcel(Parcel parcel) {
+            boolean enableReceiver = parcel.readBoolean();
+            int bufferSize = parcel.readInt();
+            return new ContentProtectionOptions(enableReceiver, bufferSize);
+        }
+    }
 }
diff --git a/core/java/android/content/pm/PackageInstaller.java b/core/java/android/content/pm/PackageInstaller.java
index 96118f6..105b38a 100644
--- a/core/java/android/content/pm/PackageInstaller.java
+++ b/core/java/android/content/pm/PackageInstaller.java
@@ -444,6 +444,10 @@
      * exist, it may be missing native code for the ABIs supported by the
      * device, or it requires a newer SDK version, etc.
      *
+     * Starting in {@link Build.VERSION_CODES#UPSIDE_DOWN_CAKE}, an app with only 32-bit native
+     * code can still be installed on a device that supports both 64-bit and 32-bit ABIs.
+     * However, a warning dialog will be displayed when the app is launched.
+     *
      * @see #EXTRA_STATUS_MESSAGE
      */
     public static final int STATUS_FAILURE_INCOMPATIBLE = 7;
diff --git a/core/java/android/hardware/input/InputSettings.java b/core/java/android/hardware/input/InputSettings.java
index 17bbe14..33960c0 100644
--- a/core/java/android/hardware/input/InputSettings.java
+++ b/core/java/android/hardware/input/InputSettings.java
@@ -266,7 +266,7 @@
      */
     public static boolean useTouchpadTapToClick(@NonNull Context context) {
         return Settings.System.getIntForUser(context.getContentResolver(),
-                Settings.System.TOUCHPAD_TAP_TO_CLICK, 0, UserHandle.USER_CURRENT) == 1;
+                Settings.System.TOUCHPAD_TAP_TO_CLICK, 1, UserHandle.USER_CURRENT) == 1;
     }
 
     /**
diff --git a/core/java/android/service/wallpaper/WallpaperService.java b/core/java/android/service/wallpaper/WallpaperService.java
index 7f0a666..4e086c2 100644
--- a/core/java/android/service/wallpaper/WallpaperService.java
+++ b/core/java/android/service/wallpaper/WallpaperService.java
@@ -1391,7 +1391,7 @@
                             Trace.endSection();
                         }
 
-                        if (redrawNeeded) {
+                        if (redrawNeeded || sizeChanged) {
                             Trace.beginSection("WPMS.Engine.onSurfaceRedrawNeeded");
                             onSurfaceRedrawNeeded(mSurfaceHolder);
                             Trace.endSection();
diff --git a/core/java/android/view/MotionEvent.java b/core/java/android/view/MotionEvent.java
index 1af8ca2..c703af5 100644
--- a/core/java/android/view/MotionEvent.java
+++ b/core/java/android/view/MotionEvent.java
@@ -1285,6 +1285,8 @@
      * </ul>
      * These values are relative to the state from the last event, not accumulated, so developers
      * should make sure to process this axis value for all batched historical events.
+     * <p>
+     * This axis is only set on the first pointer in a motion event.
      */
     public static final int AXIS_GESTURE_X_OFFSET = 48;
 
@@ -1304,6 +1306,8 @@
      * </ul>
      * These values are relative to the state from the last event, not accumulated, so developers
      * should make sure to process this axis value for all batched historical events.
+     * <p>
+     * This axis is only set on the first pointer in a motion event.
      */
     public static final int AXIS_GESTURE_SCROLL_X_DISTANCE = 50;
 
@@ -1324,14 +1328,29 @@
      * </ul>
      * These values are relative to the state from the last event, not accumulated, so developers
      * should make sure to process this axis value for all batched historical events.
+     * <p>
+     * This axis is only set on the first pointer in a motion event.
      */
     public static final int AXIS_GESTURE_PINCH_SCALE_FACTOR = 52;
 
+    /**
+     * Axis constant: the number of fingers being used in a multi-finger swipe gesture.
+     * <p>
+     * <ul>
+     * <li>For a touch pad, reports the number of fingers being used in a multi-finger swipe gesture
+     * (with CLASSIFICATION_MULTI_FINGER_SWIPE).
+     * </ul>
+     * <p>
+     * Since CLASSIFICATION_MULTI_FINGER_SWIPE is a hidden API, so is this axis. It is only set on
+     * the first pointer in a motion event.
+     * @hide
+     */
+    public static final int AXIS_GESTURE_SWIPE_FINGER_COUNT = 53;
+
     // NOTE: If you add a new axis here you must also add it to:
     //  frameworks/native/include/android/input.h
     //  frameworks/native/libs/input/InputEventLabels.cpp
-    //  platform/cts/tests/tests/view/src/android/view/cts/MotionEventTest.java
-    //    (testAxisFromToString)
+    //  cts/tests/tests/view/src/android/view/cts/MotionEventTest.java (testAxisFromToString)
 
     // Symbolic names of all axes.
     private static final SparseArray<String> AXIS_SYMBOLIC_NAMES = new SparseArray<String>();
@@ -1387,6 +1406,7 @@
         names.append(AXIS_GESTURE_SCROLL_X_DISTANCE, "AXIS_GESTURE_SCROLL_X_DISTANCE");
         names.append(AXIS_GESTURE_SCROLL_Y_DISTANCE, "AXIS_GESTURE_SCROLL_Y_DISTANCE");
         names.append(AXIS_GESTURE_PINCH_SCALE_FACTOR, "AXIS_GESTURE_PINCH_SCALE_FACTOR");
+        names.append(AXIS_GESTURE_SWIPE_FINGER_COUNT, "AXIS_GESTURE_SWIPE_FINGER_COUNT");
     }
 
     /**
diff --git a/core/java/android/view/SurfaceControl.java b/core/java/android/view/SurfaceControl.java
index ddaa71c..c11f497 100644
--- a/core/java/android/view/SurfaceControl.java
+++ b/core/java/android/view/SurfaceControl.java
@@ -786,7 +786,11 @@
             mReleaseStack = null;
         }
         setUnreleasedWarningCallSite(callsite);
-        addToRegistry();
+        if (nativeObject != 0) {
+            // Only add valid surface controls to the registry. This is called at the end of this
+            // method since its information is dumped if the process threshold is reached.
+            addToRegistry();
+        }
     }
 
     /**
@@ -893,6 +897,10 @@
                         "Only buffer layers can set a valid buffer size.");
             }
 
+            if (mName == null) {
+                Log.w(TAG, "Missing name for SurfaceControl", new Throwable());
+            }
+
             if ((mFlags & FX_SURFACE_MASK) == FX_SURFACE_NORMAL) {
                 setBLASTLayer();
             }
@@ -1254,6 +1262,9 @@
     }
 
     /**
+     * Note: Most callers should use {@link SurfaceControl.Builder} or one of the other constructors
+     *       to build an instance of a SurfaceControl. This constructor is mainly used for
+     *       unparceling and passing into an AIDL call as an out parameter.
      * @hide
      */
     public SurfaceControl() {
@@ -2495,6 +2506,7 @@
     public static SurfaceControl mirrorSurface(SurfaceControl mirrorOf) {
         long nativeObj = nativeMirrorSurface(mirrorOf.mNativeObject);
         SurfaceControl sc = new SurfaceControl();
+        sc.mName = mirrorOf.mName + " (mirror)";
         sc.assignNativeObject(nativeObj, "mirrorSurface");
         return sc;
     }
diff --git a/core/java/android/view/SurfaceControlViewHost.java b/core/java/android/view/SurfaceControlViewHost.java
index d987217..c8cf7d9 100644
--- a/core/java/android/view/SurfaceControlViewHost.java
+++ b/core/java/android/view/SurfaceControlViewHost.java
@@ -171,8 +171,7 @@
         public SurfacePackage(@NonNull SurfacePackage other) {
             SurfaceControl otherSurfaceControl = other.mSurfaceControl;
             if (otherSurfaceControl != null && otherSurfaceControl.isValid()) {
-                mSurfaceControl = new SurfaceControl();
-                mSurfaceControl.copyFrom(otherSurfaceControl, "SurfacePackage");
+                mSurfaceControl = new SurfaceControl(otherSurfaceControl, "SurfacePackage");
             }
             mAccessibilityEmbeddedConnection = other.mAccessibilityEmbeddedConnection;
             mInputToken = other.mInputToken;
diff --git a/core/java/android/view/contentcapture/ContentCaptureManager.java b/core/java/android/view/contentcapture/ContentCaptureManager.java
index 668351b..c37e311 100644
--- a/core/java/android/view/contentcapture/ContentCaptureManager.java
+++ b/core/java/android/view/contentcapture/ContentCaptureManager.java
@@ -352,6 +352,30 @@
     public static final String DEVICE_CONFIG_PROPERTY_DISABLE_FLUSH_FOR_VIEW_TREE_APPEARING =
             "disable_flush_for_view_tree_appearing";
 
+    /**
+     * Enables the content protection receiver.
+     *
+     * @hide
+     */
+    public static final String DEVICE_CONFIG_PROPERTY_ENABLE_CONTENT_PROTECTION_RECEIVER =
+            "enable_content_protection_receiver";
+
+    /**
+     * Sets the size of the app blocklist for the content protection flow.
+     *
+     * @hide
+     */
+    public static final String DEVICE_CONFIG_PROPERTY_CONTENT_PROTECTION_APPS_BLOCKLIST_SIZE =
+            "content_protection_apps_blocklist_size";
+
+    /**
+     * Sets the size of the in-memory ring buffer for the content protection flow.
+     *
+     * @hide
+     */
+    public static final String DEVICE_CONFIG_PROPERTY_CONTENT_PROTECTION_BUFFER_SIZE =
+            "content_protection_buffer_size";
+
     /** @hide */
     @TestApi
     public static final int LOGGING_LEVEL_OFF = 0;
@@ -384,6 +408,14 @@
     public static final int DEFAULT_LOG_HISTORY_SIZE = 10;
     /** @hide */
     public static final boolean DEFAULT_DISABLE_FLUSH_FOR_VIEW_TREE_APPEARING = false;
+    /** @hide */
+    public static final boolean DEFAULT_ENABLE_CONTENT_CAPTURE_RECEIVER = true;
+    /** @hide */
+    public static final boolean DEFAULT_ENABLE_CONTENT_PROTECTION_RECEIVER = false;
+    /** @hide */
+    public static final int DEFAULT_CONTENT_PROTECTION_APPS_BLOCKLIST_SIZE = 1000;
+    /** @hide */
+    public static final int DEFAULT_CONTENT_PROTECTION_BUFFER_SIZE = 150;
 
     private final Object mLock = new Object();
 
@@ -425,11 +457,11 @@
 
     /** @hide */
     static class StrippedContext {
-        final String mPackageName;
-        final String mContext;
+        @NonNull final String mPackageName;
+        @NonNull final String mContext;
         final @UserIdInt int mUserId;
 
-        private StrippedContext(Context context) {
+        private StrippedContext(@NonNull Context context) {
             mPackageName = context.getPackageName();
             mContext = context.toString();
             mUserId = context.getUserId();
@@ -440,6 +472,7 @@
             return mContext;
         }
 
+        @NonNull
         public String getPackageName() {
             return mPackageName;
         }
diff --git a/core/java/android/view/contentprotection/ContentProtectionUtils.java b/core/java/android/view/contentprotection/ContentProtectionUtils.java
new file mode 100644
index 0000000..9abf6f1
--- /dev/null
+++ b/core/java/android/view/contentprotection/ContentProtectionUtils.java
@@ -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 android.view.contentprotection;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.view.contentcapture.ContentCaptureEvent;
+import android.view.contentcapture.ViewNode;
+
+/**
+ * Utilities for reading data from {@link ContentCaptureEvent} and {@link ViewNode}.
+ *
+ * @hide
+ */
+public final class ContentProtectionUtils {
+
+    /** Returns the text extracted directly from the {@link ContentCaptureEvent}, if set. */
+    @Nullable
+    public static String getEventText(@NonNull ContentCaptureEvent event) {
+        CharSequence text = event.getText();
+        if (text == null) {
+            return null;
+        }
+        return text.toString();
+    }
+
+    /** Returns the text extracted from the event's {@link ViewNode}, if set. */
+    @Nullable
+    public static String getViewNodeText(@NonNull ContentCaptureEvent event) {
+        ViewNode viewNode = event.getViewNode();
+        if (viewNode == null) {
+            return null;
+        }
+        return getViewNodeText(viewNode);
+    }
+
+    /** Returns the text extracted directly from the {@link ViewNode}, if set. */
+    @Nullable
+    public static String getViewNodeText(@NonNull ViewNode viewNode) {
+        CharSequence text = viewNode.getText();
+        if (text == null) {
+            return null;
+        }
+        return text.toString();
+    }
+}
diff --git a/core/java/android/view/contentprotection/OWNERS b/core/java/android/view/contentprotection/OWNERS
new file mode 100644
index 0000000..b3583a7
--- /dev/null
+++ b/core/java/android/view/contentprotection/OWNERS
@@ -0,0 +1,4 @@
+# Bug component: 544200
+
+include /core/java/android/view/contentcapture/OWNERS
+
diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java
index 41ef44e..40b060a 100644
--- a/core/java/android/view/inputmethod/InputMethodManager.java
+++ b/core/java/android/view/inputmethod/InputMethodManager.java
@@ -4361,15 +4361,14 @@
      * @param icProto {@link InputConnection} call data in proto format.
      * @hide
      */
-    @GuardedBy("mH")
     public void dumpDebug(ProtoOutputStream proto, @Nullable byte[] icProto) {
-        if (!isImeSessionAvailableLocked()) {
-            return;
-        }
-
-        proto.write(DISPLAY_ID, mDisplayId);
-        final long token = proto.start(INPUT_METHOD_MANAGER);
         synchronized (mH) {
+            if (!isImeSessionAvailableLocked()) {
+                return;
+            }
+
+            proto.write(DISPLAY_ID, mDisplayId);
+            final long token = proto.start(INPUT_METHOD_MANAGER);
             proto.write(CUR_ID, mCurBindState.mImeId);
             proto.write(FULLSCREEN_MODE, mFullscreenMode);
             proto.write(ACTIVE, mActive);
diff --git a/core/java/android/widget/RemoteViews.java b/core/java/android/widget/RemoteViews.java
index dedbf50..7f96266 100644
--- a/core/java/android/widget/RemoteViews.java
+++ b/core/java/android/widget/RemoteViews.java
@@ -1850,7 +1850,7 @@
         }
 
         @Override
-        public final void visitUris(@NonNull Consumer<Uri> visitor) {
+        public void visitUris(@NonNull Consumer<Uri> visitor) {
             switch (this.type) {
                 case URI:
                     final Uri uri = (Uri) getParameterValue(null);
@@ -2313,6 +2313,14 @@
         public int getActionTag() {
             return NIGHT_MODE_REFLECTION_ACTION_TAG;
         }
+
+        @Override
+        public void visitUris(@NonNull Consumer<Uri> visitor) {
+            if (this.type == ICON) {
+                visitIconUri((Icon) mDarkValue, visitor);
+                visitIconUri((Icon) mLightValue, visitor);
+            }
+        }
     }
 
     /**
diff --git a/core/java/android/window/SnapshotDrawerUtils.java b/core/java/android/window/SnapshotDrawerUtils.java
index 52e17ca..f40874b 100644
--- a/core/java/android/window/SnapshotDrawerUtils.java
+++ b/core/java/android/window/SnapshotDrawerUtils.java
@@ -43,6 +43,7 @@
 import static com.android.internal.policy.DecorView.STATUS_BAR_COLOR_VIEW_ATTRIBUTES;
 import static com.android.internal.policy.DecorView.getNavigationBarRect;
 
+import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.app.ActivityManager;
 import android.app.ActivityThread;
@@ -183,7 +184,7 @@
 
             // We consider nearly matched dimensions as there can be rounding errors and the user
             // won't notice very minute differences from scaling one dimension more than the other
-            final boolean aspectRatioMismatch = !isAspectRatioMatch(mFrame, mSnapshot);
+            boolean aspectRatioMismatch = !isAspectRatioMatch(mFrame, mSnapshot);
 
             // Keep a reference to it such that it doesn't get destroyed when finalized.
             SurfaceControl childSurfaceControl = new SurfaceControl.Builder(session)
@@ -199,8 +200,20 @@
             // still hidden.
             mTransaction.show(childSurfaceControl);
             if (aspectRatioMismatch) {
-                // Clip off ugly navigation bar.
-                final Rect crop = calculateSnapshotCrop();
+                Rect crop = null;
+                final Rect letterboxInsets = mSnapshot.getLetterboxInsets();
+                if (letterboxInsets.left != 0 || letterboxInsets.top != 0
+                        || letterboxInsets.right != 0 || letterboxInsets.bottom != 0) {
+                    // Clip off letterbox.
+                    crop = calculateSnapshotCrop(letterboxInsets);
+                    // If the snapshot can cover the frame, then no need to draw background.
+                    aspectRatioMismatch = !isAspectRatioMatch(mFrame, crop);
+                }
+                // if letterbox doesn't match window frame, try crop by content insets
+                if (aspectRatioMismatch) {
+                    // Clip off ugly navigation bar.
+                    crop = calculateSnapshotCrop(mSnapshot.getContentInsets());
+                }
                 frame = calculateSnapshotFrame(crop);
                 mTransaction.setWindowCrop(childSurfaceControl, crop);
                 mTransaction.setPosition(childSurfaceControl, frame.left, frame.top);
@@ -242,14 +255,13 @@
 
         /**
          * Calculates the snapshot crop in snapshot coordinate space.
-         *
+         * @param insets Content insets or Letterbox insets
          * @return crop rect in snapshot coordinate space.
          */
-        Rect calculateSnapshotCrop() {
+        Rect calculateSnapshotCrop(@NonNull Rect insets) {
             final Rect rect = new Rect();
             final HardwareBuffer snapshot = mSnapshot.getHardwareBuffer();
             rect.set(0, 0, snapshot.getWidth(), snapshot.getHeight());
-            final Rect insets = mSnapshot.getContentInsets();
 
             final float scaleX = (float) snapshot.getWidth() / mSnapshot.getTaskSize().x;
             final float scaleY = (float) snapshot.getHeight() / mSnapshot.getTaskSize().y;
@@ -334,6 +346,15 @@
                         - ((float) frame.width() / frame.height())) <= 0.01f;
     }
 
+    private static boolean isAspectRatioMatch(Rect frame1, Rect frame2) {
+        if (frame1.isEmpty() || frame2.isEmpty()) {
+            return false;
+        }
+        return Math.abs(
+                ((float) frame2.width() / frame2.height())
+                        - ((float) frame1.width() / frame1.height())) <= 0.01f;
+    }
+
     /**
      * Get or create a TaskDescription from a RunningTaskInfo.
      */
diff --git a/core/java/android/window/SurfaceSyncGroup.java b/core/java/android/window/SurfaceSyncGroup.java
index dfdff9e..5d14698 100644
--- a/core/java/android/window/SurfaceSyncGroup.java
+++ b/core/java/android/window/SurfaceSyncGroup.java
@@ -26,6 +26,7 @@
 import android.os.Handler;
 import android.os.HandlerThread;
 import android.os.IBinder;
+import android.os.Looper;
 import android.os.RemoteException;
 import android.os.Trace;
 import android.util.ArraySet;
@@ -800,22 +801,25 @@
     }
 
     private void addTimeout() {
+        Looper looper = null;
         synchronized (sHandlerThreadLock) {
             if (sHandlerThread == null) {
                 sHandlerThread = new HandlerThread("SurfaceSyncGroupTimer");
                 sHandlerThread.start();
             }
+
+            looper = sHandlerThread.getLooper();
         }
 
         synchronized (mLock) {
-            if (mTimeoutAdded || mTimeoutDisabled) {
+            if (mTimeoutAdded || mTimeoutDisabled || looper == null) {
                 // We only need one timeout for the entire SurfaceSyncGroup since we just want to
                 // ensure it doesn't stay stuck forever.
                 return;
             }
 
             if (mHandler == null) {
-                mHandler = new Handler(sHandlerThread.getLooper());
+                mHandler = new Handler(looper);
             }
 
             mTimeoutAdded = true;
diff --git a/core/java/android/window/WindowOnBackInvokedDispatcher.java b/core/java/android/window/WindowOnBackInvokedDispatcher.java
index 22b2ec0..421d998 100644
--- a/core/java/android/window/WindowOnBackInvokedDispatcher.java
+++ b/core/java/android/window/WindowOnBackInvokedDispatcher.java
@@ -31,6 +31,8 @@
 import android.view.IWindow;
 import android.view.IWindowSession;
 
+import androidx.annotation.VisibleForTesting;
+
 import java.io.PrintWriter;
 import java.lang.ref.WeakReference;
 import java.util.ArrayList;
@@ -66,7 +68,9 @@
     /** Convenience hashmap to quickly decide if a callback has been added. */
     private final HashMap<OnBackInvokedCallback, Integer> mAllCallbacks = new HashMap<>();
     /** Holds all callbacks by priorities. */
-    private final TreeMap<Integer, ArrayList<OnBackInvokedCallback>>
+
+    @VisibleForTesting
+    public final TreeMap<Integer, ArrayList<OnBackInvokedCallback>>
             mOnBackInvokedCallbacks = new TreeMap<>();
     private Checker mChecker;
 
diff --git a/core/java/com/android/internal/accessibility/util/AccessibilityStatsLogUtils.java b/core/java/com/android/internal/accessibility/util/AccessibilityStatsLogUtils.java
index 1f7640d..7c4252e 100644
--- a/core/java/com/android/internal/accessibility/util/AccessibilityStatsLogUtils.java
+++ b/core/java/com/android/internal/accessibility/util/AccessibilityStatsLogUtils.java
@@ -258,6 +258,10 @@
     }
 
     private static int convertToLoggingMagnificationScale(float scale) {
-        return (int) (scale * 100);
+        // per b/269366674, we make every 10% a bucket for both privacy and readability concern.
+        // For example
+        // 1. both 2.30f(230%) and 2.36f(236%) would return 230 as bucket id.
+        // 2. bucket id 370 means scale range in [370%, 379%]
+        return ((int) (scale * 10)) * 10;
     }
 }
diff --git a/core/java/com/android/internal/config/sysui/SystemUiSystemPropertiesFlags.java b/core/java/com/android/internal/config/sysui/SystemUiSystemPropertiesFlags.java
index 86c2893..3d95dd3 100644
--- a/core/java/com/android/internal/config/sysui/SystemUiSystemPropertiesFlags.java
+++ b/core/java/com/android/internal/config/sysui/SystemUiSystemPropertiesFlags.java
@@ -80,7 +80,7 @@
 
         /** Gating the logging of DND state change events. */
         public static final Flag LOG_DND_STATE_EVENTS =
-                devFlag("persist.sysui.notification.log_dnd_state_events");
+                releasedFlag("persist.sysui.notification.log_dnd_state_events");
     }
 
     //// == End of flags.  Everything below this line is the implementation. == ////
diff --git a/core/res/res/layout/language_picker_section_header.xml b/core/res/res/layout/language_picker_section_header.xml
index 58042f9..54ac677 100644
--- a/core/res/res/layout/language_picker_section_header.xml
+++ b/core/res/res/layout/language_picker_section_header.xml
@@ -25,4 +25,5 @@
           android:textColor="?android:attr/colorAccent"
           android:textStyle="bold"
           android:id="@+id/language_picker_header"
-          tools:text="@string/language_picker_section_all"/>
+          tools:text="@string/language_picker_section_all"
+          android:scrollbars="none"/>
diff --git a/core/tests/coretests/src/android/content/ContentCaptureOptionsTest.java b/core/tests/coretests/src/android/content/ContentCaptureOptionsTest.java
index c6f4fa2..f8348d2 100644
--- a/core/tests/coretests/src/android/content/ContentCaptureOptionsTest.java
+++ b/core/tests/coretests/src/android/content/ContentCaptureOptionsTest.java
@@ -22,6 +22,7 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.os.Parcel;
 import android.util.ArraySet;
 import android.view.contentcapture.ContentCaptureManager.ContentCaptureClient;
 
@@ -40,16 +41,29 @@
 @RunWith(MockitoJUnitRunner.class)
 public class ContentCaptureOptionsTest {
 
-    private final ComponentName mContextComponent = new ComponentName("marco", "polo");
-    private final ComponentName mComp1 = new ComponentName("comp", "one");
-    private final ComponentName mComp2 = new ComponentName("two", "comp");
+    private static final ComponentName CONTEXT_COMPONENT = new ComponentName("marco", "polo");
+    private static final ComponentName COMPONENT1 = new ComponentName("comp", "one");
+    private static final ComponentName COMPONENT2 = new ComponentName("two", "comp");
+    private static final ContentCaptureOptions CONTENT_CAPTURE_OPTIONS =
+            new ContentCaptureOptions(
+                    /* loggingLevel= */ 1000,
+                    /* maxBufferSize= */ 1001,
+                    /* idleFlushingFrequencyMs= */ 1002,
+                    /* textChangeFlushingFrequencyMs= */ 1003,
+                    /* logHistorySize= */ 1004,
+                    /* disableFlushForViewTreeAppearing= */ true,
+                    /* enableReceiver= */ false,
+                    new ContentCaptureOptions.ContentProtectionOptions(
+                            /* enableReceiver= */ true,
+                            /* bufferSize= */ 2001),
+                    /* whitelistedComponents= */ toSet(COMPONENT1, COMPONENT2));
 
     @Mock private Context mContext;
     @Mock private ContentCaptureClient mClient;
 
     @Before
     public void setExpectation() {
-        when(mClient.contentCaptureClientGetComponentName()).thenReturn(mContextComponent);
+        when(mClient.contentCaptureClientGetComponentName()).thenReturn(CONTEXT_COMPONENT);
         when(mContext.getContentCaptureClient()).thenReturn(mClient);
     }
 
@@ -67,26 +81,27 @@
 
     @Test
     public void testIsWhitelisted_notWhitelisted() {
-        ContentCaptureOptions options = new ContentCaptureOptions(toSet(mComp1, mComp2));
+        ContentCaptureOptions options = new ContentCaptureOptions(toSet(COMPONENT1, COMPONENT2));
         assertThat(options.isWhitelisted(mContext)).isFalse();
     }
 
     @Test
     public void testIsWhitelisted_whitelisted() {
-        ContentCaptureOptions options = new ContentCaptureOptions(toSet(mComp1, mContextComponent));
+        ContentCaptureOptions options =
+                new ContentCaptureOptions(toSet(COMPONENT1, CONTEXT_COMPONENT));
         assertThat(options.isWhitelisted(mContext)).isTrue();
     }
 
     @Test
     public void testIsWhitelisted_invalidContext() {
-        ContentCaptureOptions options = new ContentCaptureOptions(toSet(mContextComponent));
+        ContentCaptureOptions options = new ContentCaptureOptions(toSet(CONTEXT_COMPONENT));
         Context invalidContext = mock(Context.class); // has no client
         assertThat(options.isWhitelisted(invalidContext)).isFalse();
     }
 
     @Test
     public void testIsWhitelisted_clientWithNullComponentName() {
-        ContentCaptureOptions options = new ContentCaptureOptions(toSet(mContextComponent));
+        ContentCaptureOptions options = new ContentCaptureOptions(toSet(CONTEXT_COMPONENT));
         ContentCaptureClient client = mock(ContentCaptureClient.class);
         Context context = mock(Context.class);
         when(context.getContentCaptureClient()).thenReturn(client);
@@ -94,8 +109,69 @@
         assertThat(options.isWhitelisted(context)).isFalse();
     }
 
+    @Test
+    public void testToString() {
+        String actual = CONTENT_CAPTURE_OPTIONS.toString();
+
+        String expected =
+                new StringBuilder("ContentCaptureOptions [")
+                        .append("loggingLevel=")
+                        .append(CONTENT_CAPTURE_OPTIONS.loggingLevel)
+                        .append(", maxBufferSize=")
+                        .append(CONTENT_CAPTURE_OPTIONS.maxBufferSize)
+                        .append(", idleFlushingFrequencyMs=")
+                        .append(CONTENT_CAPTURE_OPTIONS.idleFlushingFrequencyMs)
+                        .append(", textChangeFlushingFrequencyMs=")
+                        .append(CONTENT_CAPTURE_OPTIONS.textChangeFlushingFrequencyMs)
+                        .append(", logHistorySize=")
+                        .append(CONTENT_CAPTURE_OPTIONS.logHistorySize)
+                        .append(", disableFlushForViewTreeAppearing=")
+                        .append(CONTENT_CAPTURE_OPTIONS.disableFlushForViewTreeAppearing)
+                        .append(", enableReceiver=")
+                        .append(CONTENT_CAPTURE_OPTIONS.enableReceiver)
+                        .append(", contentProtectionOptions=ContentProtectionOptions [")
+                        .append("enableReceiver=")
+                        .append(CONTENT_CAPTURE_OPTIONS.contentProtectionOptions.enableReceiver)
+                        .append(", bufferSize=")
+                        .append(CONTENT_CAPTURE_OPTIONS.contentProtectionOptions.bufferSize)
+                        .append("], whitelisted=")
+                        .append(CONTENT_CAPTURE_OPTIONS.whitelistedComponents)
+                        .append(']')
+                        .toString();
+        assertThat(actual).isEqualTo(expected);
+    }
+
+    @Test
+    public void testParcelSerializationDeserialization() {
+        Parcel parcel = Parcel.obtain();
+        CONTENT_CAPTURE_OPTIONS.writeToParcel(parcel, /* flags= */ 0);
+        parcel.setDataPosition(0);
+
+        ContentCaptureOptions actual = ContentCaptureOptions.CREATOR.createFromParcel(parcel);
+        parcel.recycle();
+
+        assertThat(actual).isNotNull();
+        assertThat(actual.loggingLevel).isEqualTo(CONTENT_CAPTURE_OPTIONS.loggingLevel);
+        assertThat(actual.maxBufferSize).isEqualTo(CONTENT_CAPTURE_OPTIONS.maxBufferSize);
+        assertThat(actual.idleFlushingFrequencyMs)
+                .isEqualTo(CONTENT_CAPTURE_OPTIONS.idleFlushingFrequencyMs);
+        assertThat(actual.textChangeFlushingFrequencyMs)
+                .isEqualTo(CONTENT_CAPTURE_OPTIONS.textChangeFlushingFrequencyMs);
+        assertThat(actual.logHistorySize).isEqualTo(CONTENT_CAPTURE_OPTIONS.logHistorySize);
+        assertThat(actual.disableFlushForViewTreeAppearing)
+                .isEqualTo(CONTENT_CAPTURE_OPTIONS.disableFlushForViewTreeAppearing);
+        assertThat(actual.enableReceiver).isEqualTo(CONTENT_CAPTURE_OPTIONS.enableReceiver);
+        assertThat(actual.contentProtectionOptions).isNotNull();
+        assertThat(actual.contentProtectionOptions.enableReceiver)
+                .isEqualTo(CONTENT_CAPTURE_OPTIONS.contentProtectionOptions.enableReceiver);
+        assertThat(actual.contentProtectionOptions.bufferSize)
+                .isEqualTo(CONTENT_CAPTURE_OPTIONS.contentProtectionOptions.bufferSize);
+        assertThat(actual.whitelistedComponents)
+                .containsExactlyElementsIn(CONTENT_CAPTURE_OPTIONS.whitelistedComponents);
+    }
+
     @NonNull
-    private ArraySet<ComponentName> toSet(@Nullable ComponentName... comps) {
+    private static ArraySet<ComponentName> toSet(@Nullable ComponentName... comps) {
         ArraySet<ComponentName> set = new ArraySet<>();
         if (comps != null) {
             for (int i = 0; i < comps.length; i++) {
diff --git a/core/tests/coretests/src/android/content/OWNERS b/core/tests/coretests/src/android/content/OWNERS
index a69c6ff..6f38b84 100644
--- a/core/tests/coretests/src/android/content/OWNERS
+++ b/core/tests/coretests/src/android/content/OWNERS
@@ -5,3 +5,5 @@
 per-file *Shortcut* = file:/core/java/android/content/pm/SHORTCUT_OWNERS
 per-file *ComponentCallbacks* = file:/services/core/java/com/android/server/wm/OWNERS
 per-file *ComponentCallbacks* = charlesccchen@google.com
+per-file ContentCaptureOptions* = file:/core/java/android/view/contentcapture/OWNERS
+
diff --git a/core/tests/coretests/src/android/view/SurfaceControlRegistryTests.java b/core/tests/coretests/src/android/view/SurfaceControlRegistryTests.java
index d10ba7c..e117051 100644
--- a/core/tests/coretests/src/android/view/SurfaceControlRegistryTests.java
+++ b/core/tests/coretests/src/android/view/SurfaceControlRegistryTests.java
@@ -104,6 +104,18 @@
     }
 
     @Test
+    public void testInvalidSurfaceControlNotAddedToRegistry() {
+        int hash0 = SurfaceControlRegistry.getProcessInstance().hashCode();
+        // Verify no changes to the registry when dealing with invalid surface controls
+        SurfaceControl sc0 = new SurfaceControl();
+        SurfaceControl sc1 = new SurfaceControl(sc0, "test");
+        assertEquals(hash0, SurfaceControlRegistry.getProcessInstance().hashCode());
+        sc0.release();
+        sc1.release();
+        assertEquals(hash0, SurfaceControlRegistry.getProcessInstance().hashCode());
+    }
+
+    @Test
     public void testThresholds() {
         SurfaceControlRegistry registry = SurfaceControlRegistry.getProcessInstance();
         TestReporter reporter = new TestReporter();
diff --git a/core/tests/coretests/src/android/view/contentprotection/ContentProtectionUtilsTest.java b/core/tests/coretests/src/android/view/contentprotection/ContentProtectionUtilsTest.java
new file mode 100644
index 0000000..1459799
--- /dev/null
+++ b/core/tests/coretests/src/android/view/contentprotection/ContentProtectionUtilsTest.java
@@ -0,0 +1,111 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view.contentprotection;
+
+import static android.view.contentcapture.ContentCaptureEvent.TYPE_SESSION_STARTED;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.view.View;
+import android.view.contentcapture.ContentCaptureEvent;
+import android.view.contentcapture.ViewNode;
+import android.view.contentcapture.ViewNode.ViewStructureImpl;
+
+import androidx.test.core.app.ApplicationProvider;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Test for {@link ContentProtectionUtils}.
+ *
+ * <p>Run with: {@code atest
+ * FrameworksCoreTests:android.view.contentprotection.ContentProtectionUtilsTest}
+ */
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class ContentProtectionUtilsTest {
+
+    private static final String TEXT = "TEST_TEXT";
+
+    private static final ContentCaptureEvent EVENT = createEvent();
+
+    private static final ViewNode VIEW_NODE = new ViewNode();
+
+    private static final ViewNode VIEW_NODE_WITH_TEXT = createViewNodeWithText();
+
+    @Test
+    public void event_getEventText_null() {
+        String actual = ContentProtectionUtils.getEventText(EVENT);
+
+        assertThat(actual).isNull();
+    }
+
+    @Test
+    public void event_getEventText_notNull() {
+        ContentCaptureEvent event = createEvent();
+        event.setText(TEXT);
+
+        String actual = ContentProtectionUtils.getEventText(event);
+
+        assertThat(actual).isEqualTo(TEXT);
+    }
+
+    @Test
+    public void event_getViewNodeText_null() {
+        String actual = ContentProtectionUtils.getViewNodeText(EVENT);
+
+        assertThat(actual).isNull();
+    }
+
+    @Test
+    public void event_getViewNodeText_notNull() {
+        ContentCaptureEvent event = createEvent();
+        event.setViewNode(VIEW_NODE_WITH_TEXT);
+
+        String actual = ContentProtectionUtils.getViewNodeText(event);
+
+        assertThat(actual).isEqualTo(TEXT);
+    }
+
+    @Test
+    public void viewNode_getViewNodeText_null() {
+        String actual = ContentProtectionUtils.getViewNodeText(VIEW_NODE);
+
+        assertThat(actual).isNull();
+    }
+
+    @Test
+    public void viewNode_getViewNodeText_notNull() {
+        String actual = ContentProtectionUtils.getViewNodeText(VIEW_NODE_WITH_TEXT);
+
+        assertThat(actual).isEqualTo(TEXT);
+    }
+
+    private static ContentCaptureEvent createEvent() {
+        return new ContentCaptureEvent(/* sessionId= */ 123, TYPE_SESSION_STARTED);
+    }
+
+    private static ViewNode createViewNodeWithText() {
+        View view = new View(ApplicationProvider.getApplicationContext());
+        ViewStructureImpl viewStructure = new ViewStructureImpl(view);
+        viewStructure.setText(TEXT);
+        return viewStructure.getNode();
+    }
+}
diff --git a/core/tests/coretests/src/android/view/contentprotection/OWNERS b/core/tests/coretests/src/android/view/contentprotection/OWNERS
new file mode 100644
index 0000000..b3583a7
--- /dev/null
+++ b/core/tests/coretests/src/android/view/contentprotection/OWNERS
@@ -0,0 +1,4 @@
+# Bug component: 544200
+
+include /core/java/android/view/contentcapture/OWNERS
+
diff --git a/core/tests/coretests/src/android/widget/RemoteViewsTest.java b/core/tests/coretests/src/android/widget/RemoteViewsTest.java
index b42a4bd..73aa936 100644
--- a/core/tests/coretests/src/android/widget/RemoteViewsTest.java
+++ b/core/tests/coretests/src/android/widget/RemoteViewsTest.java
@@ -717,6 +717,19 @@
     }
 
     @Test
+    public void visitUris_themedIcons() {
+        RemoteViews views = new RemoteViews(mPackage, R.layout.remote_views_test);
+        final Icon iconLight = Icon.createWithContentUri("content://light/icon");
+        final Icon iconDark = Icon.createWithContentUri("content://dark/icon");
+        views.setIcon(R.id.layout, "setLargeIcon", iconLight, iconDark);
+
+        Consumer<Uri> visitor = (Consumer<Uri>) spy(Consumer.class);
+        views.visitUris(visitor);
+        verify(visitor, times(1)).accept(eq(iconLight.getUri()));
+        verify(visitor, times(1)).accept(eq(iconDark.getUri()));
+    }
+
+    @Test
     public void visitUris_nestedViews() {
         final RemoteViews outer = new RemoteViews(mPackage, R.layout.remote_views_test);
 
diff --git a/core/tests/coretests/src/android/window/SnapshotDrawerUtilsTest.java b/core/tests/coretests/src/android/window/SnapshotDrawerUtilsTest.java
index 6764ac8..0361546 100644
--- a/core/tests/coretests/src/android/window/SnapshotDrawerUtilsTest.java
+++ b/core/tests/coretests/src/android/window/SnapshotDrawerUtilsTest.java
@@ -158,32 +158,42 @@
 
     @Test
     public void testCalculateSnapshotCrop() {
-        setupSurface(100, 100, new Rect(0, 10, 0, 10), 0, new Rect(0, 0, 100, 100));
-        assertEquals(new Rect(0, 0, 100, 90), mSnapshotSurface.calculateSnapshotCrop());
+        final Rect contentInsets = new Rect(0, 10, 0, 10);
+        setupSurface(100, 100, contentInsets, 0, new Rect(0, 0, 100, 100));
+        assertEquals(new Rect(0, 0, 100, 90),
+                mSnapshotSurface.calculateSnapshotCrop(contentInsets));
     }
 
     @Test
     public void testCalculateSnapshotCrop_taskNotOnTop() {
-        setupSurface(100, 100, new Rect(0, 10, 0, 10), 0, new Rect(0, 50, 100, 150));
-        assertEquals(new Rect(0, 10, 100, 90), mSnapshotSurface.calculateSnapshotCrop());
+        final Rect contentInsets = new Rect(0, 10, 0, 10);
+        setupSurface(100, 100, contentInsets, 0, new Rect(0, 50, 100, 150));
+        assertEquals(new Rect(0, 10, 100, 90),
+                mSnapshotSurface.calculateSnapshotCrop(contentInsets));
     }
 
     @Test
     public void testCalculateSnapshotCrop_navBarLeft() {
-        setupSurface(100, 100, new Rect(10, 10, 0, 0), 0, new Rect(0, 0, 100, 100));
-        assertEquals(new Rect(10, 0, 100, 100), mSnapshotSurface.calculateSnapshotCrop());
+        final Rect contentInsets = new Rect(0, 10, 0, 0);
+        setupSurface(100, 100, contentInsets, 0, new Rect(0, 0, 100, 100));
+        assertEquals(new Rect(10, 0, 100, 100),
+                mSnapshotSurface.calculateSnapshotCrop(contentInsets));
     }
 
     @Test
     public void testCalculateSnapshotCrop_navBarRight() {
-        setupSurface(100, 100, new Rect(0, 10, 10, 0), 0, new Rect(0, 0, 100, 100));
-        assertEquals(new Rect(0, 0, 90, 100), mSnapshotSurface.calculateSnapshotCrop());
+        final Rect contentInsets = new Rect(0, 10, 10, 0);
+        setupSurface(100, 100, contentInsets, 0, new Rect(0, 0, 100, 100));
+        assertEquals(new Rect(0, 0, 90, 100),
+                mSnapshotSurface.calculateSnapshotCrop(contentInsets));
     }
 
     @Test
     public void testCalculateSnapshotCrop_waterfall() {
-        setupSurface(100, 100, new Rect(5, 10, 5, 10), 0, new Rect(0, 0, 100, 100));
-        assertEquals(new Rect(5, 0, 95, 90), mSnapshotSurface.calculateSnapshotCrop());
+        final Rect contentInsets = new Rect(5, 10, 5, 10);
+        setupSurface(100, 100, contentInsets, 0, new Rect(0, 0, 100, 100));
+        assertEquals(new Rect(5, 0, 95, 90),
+                mSnapshotSurface.calculateSnapshotCrop(contentInsets));
     }
 
     @Test
diff --git a/core/tests/coretests/src/android/window/WindowOnBackInvokedDispatcherTest.java b/core/tests/coretests/src/android/window/WindowOnBackInvokedDispatcherTest.java
index 8e772a2..2ef2d3a 100644
--- a/core/tests/coretests/src/android/window/WindowOnBackInvokedDispatcherTest.java
+++ b/core/tests/coretests/src/android/window/WindowOnBackInvokedDispatcherTest.java
@@ -16,12 +16,15 @@
 
 package android.window;
 
+import static android.window.OnBackInvokedDispatcher.PRIORITY_DEFAULT;
+import static android.window.OnBackInvokedDispatcher.PRIORITY_OVERLAY;
+
 import static org.junit.Assert.assertEquals;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.isNull;
+import static org.mockito.Mockito.atLeast;
+import static org.mockito.Mockito.atMost;
 import static org.mockito.Mockito.doReturn;
-import static org.mockito.Mockito.reset;
-import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.verifyNoMoreInteractions;
 import static org.mockito.Mockito.verifyZeroInteractions;
@@ -45,6 +48,9 @@
 import org.mockito.Mockito;
 import org.mockito.MockitoAnnotations;
 
+import java.util.ArrayList;
+import java.util.List;
+
 /**
  * Tests for {@link WindowOnBackInvokedDispatcherTest}
  *
@@ -69,6 +75,8 @@
     @Mock
     private ApplicationInfo mApplicationInfo;
 
+    private int mCallbackInfoCalls = 0;
+
     private final BackMotionEvent mBackEvent = new BackMotionEvent(
             /* touchX = */ 0,
             /* touchY = */ 0,
@@ -93,105 +101,243 @@
         InstrumentationRegistry.getInstrumentation().waitForIdleSync();
     }
 
+    private List<OnBackInvokedCallbackInfo> captureCallbackInfo() throws RemoteException {
+        ArgumentCaptor<OnBackInvokedCallbackInfo> captor = ArgumentCaptor
+                .forClass(OnBackInvokedCallbackInfo.class);
+        // atLeast(0) -> get all setOnBackInvokedCallbackInfo() invocations
+        verify(mWindowSession, atLeast(0))
+                .setOnBackInvokedCallbackInfo(Mockito.eq(mWindow), captor.capture());
+        verifyNoMoreInteractions(mWindowSession);
+        return captor.getAllValues();
+    }
+
+    private OnBackInvokedCallbackInfo assertSetCallbackInfo() throws RemoteException {
+        List<OnBackInvokedCallbackInfo> callbackInfos = captureCallbackInfo();
+        int actual = callbackInfos.size();
+        assertEquals("setOnBackInvokedCallbackInfo", ++mCallbackInfoCalls, actual);
+        return callbackInfos.get(mCallbackInfoCalls - 1);
+    }
+
+    private void assertNoSetCallbackInfo() throws RemoteException {
+        List<OnBackInvokedCallbackInfo> callbackInfos = captureCallbackInfo();
+        int actual = callbackInfos.size();
+        assertEquals("No setOnBackInvokedCallbackInfo", mCallbackInfoCalls, actual);
+    }
+
+    private void assertCallbacksSize(int expectedDefault, int expectedOverlay) {
+        ArrayList<OnBackInvokedCallback> callbacksDefault = mDispatcher
+                .mOnBackInvokedCallbacks.get(PRIORITY_DEFAULT);
+        int actualSizeDefault = callbacksDefault != null ? callbacksDefault.size() : 0;
+        assertEquals("mOnBackInvokedCallbacks DEFAULT size", expectedDefault, actualSizeDefault);
+
+        ArrayList<OnBackInvokedCallback> callbacksOverlay = mDispatcher
+                .mOnBackInvokedCallbacks.get(PRIORITY_OVERLAY);
+        int actualSizeOverlay = callbacksOverlay != null ? callbacksOverlay.size() : 0;
+        assertEquals("mOnBackInvokedCallbacks OVERLAY size", expectedOverlay, actualSizeOverlay);
+    }
+
+    private void assertTopCallback(OnBackInvokedCallback expectedCallback) {
+        assertEquals("topCallback", expectedCallback, mDispatcher.getTopCallback());
+    }
+
+    @Test
+    public void registerCallback_samePriority_sameCallback() throws RemoteException {
+        mDispatcher.registerOnBackInvokedCallback(PRIORITY_DEFAULT, mCallback1);
+        assertCallbacksSize(/* default */ 1, /* overlay */ 0);
+        assertSetCallbackInfo();
+        assertTopCallback(mCallback1);
+
+        // The callback is removed and added again
+        mDispatcher.registerOnBackInvokedCallback(PRIORITY_DEFAULT, mCallback1);
+        assertCallbacksSize(/* default */ 1, /* overlay */ 0);
+        assertSetCallbackInfo();
+        assertTopCallback(mCallback1);
+
+        waitForIdle();
+        verifyNoMoreInteractions(mWindowSession);
+        verifyNoMoreInteractions(mCallback1);
+    }
+
+    @Test
+    public void registerCallback_samePriority_differentCallback() throws RemoteException {
+        mDispatcher.registerOnBackInvokedCallback(PRIORITY_DEFAULT, mCallback1);
+        assertCallbacksSize(/* default */ 1, /* overlay */ 0);
+        assertSetCallbackInfo();
+        assertTopCallback(mCallback1);
+
+        // The new callback becomes the TopCallback
+        mDispatcher.registerOnBackInvokedCallback(PRIORITY_DEFAULT, mCallback2);
+        assertCallbacksSize(/* default */ 2, /* overlay */ 0);
+        assertSetCallbackInfo();
+        assertTopCallback(mCallback2);
+
+        waitForIdle();
+        verifyNoMoreInteractions(mWindowSession);
+        verifyNoMoreInteractions(mCallback1);
+        verifyNoMoreInteractions(mCallback2);
+    }
+
+    @Test
+    public void registerCallback_differentPriority_sameCallback() throws RemoteException {
+        mDispatcher.registerOnBackInvokedCallback(PRIORITY_OVERLAY, mCallback1);
+        assertCallbacksSize(/* default */ 0, /* overlay */ 1);
+        assertSetCallbackInfo();
+        assertTopCallback(mCallback1);
+
+        // The callback is moved to the new priority list
+        mDispatcher.registerOnBackInvokedCallback(PRIORITY_DEFAULT, mCallback1);
+        assertCallbacksSize(/* default */ 1, /* overlay */ 0);
+        assertSetCallbackInfo();
+        assertTopCallback(mCallback1);
+
+        waitForIdle();
+        verifyNoMoreInteractions(mWindowSession);
+        verifyNoMoreInteractions(mCallback1);
+    }
+
+    @Test
+    public void registerCallback_differentPriority_differentCallback() throws RemoteException {
+        mDispatcher.registerOnBackInvokedCallback(PRIORITY_OVERLAY, mCallback1);
+        assertSetCallbackInfo();
+        assertCallbacksSize(/* default */ 0, /* overlay */ 1);
+        assertTopCallback(mCallback1);
+
+        // The callback with higher priority is still the TopCallback
+        mDispatcher.registerOnBackInvokedCallback(PRIORITY_DEFAULT, mCallback2);
+        assertNoSetCallbackInfo();
+        assertCallbacksSize(/* default */ 1, /* overlay */ 1);
+        assertTopCallback(mCallback1);
+
+        waitForIdle();
+        verifyNoMoreInteractions(mWindowSession);
+        verifyNoMoreInteractions(mCallback1);
+        verifyNoMoreInteractions(mCallback2);
+    }
+
+    @Test
+    public void registerCallback_sameInstanceAddedTwice() throws RemoteException {
+        mDispatcher.registerOnBackInvokedCallback(PRIORITY_OVERLAY, mCallback1);
+        assertCallbacksSize(/* default */ 0, /* overlay */ 1);
+        assertSetCallbackInfo();
+        assertTopCallback(mCallback1);
+
+        mDispatcher.registerOnBackInvokedCallback(PRIORITY_DEFAULT, mCallback2);
+        assertCallbacksSize(/* default */ 1, /* overlay */ 1);
+        assertNoSetCallbackInfo();
+        assertTopCallback(mCallback1);
+
+        mDispatcher.registerOnBackInvokedCallback(PRIORITY_DEFAULT, mCallback1);
+        assertCallbacksSize(/* default */ 2, /* overlay */ 0);
+        assertSetCallbackInfo();
+        assertTopCallback(mCallback1);
+
+        mDispatcher.registerOnBackInvokedCallback(PRIORITY_OVERLAY, mCallback2);
+        assertCallbacksSize(/* default */ 1, /* overlay */ 1);
+        assertSetCallbackInfo();
+        assertTopCallback(mCallback2);
+
+        waitForIdle();
+        verifyNoMoreInteractions(mWindowSession);
+        verifyNoMoreInteractions(mCallback1);
+        verifyNoMoreInteractions(mCallback2);
+    }
+
     @Test
     public void propagatesTopCallback_samePriority() throws RemoteException {
-        ArgumentCaptor<OnBackInvokedCallbackInfo> captor =
-                ArgumentCaptor.forClass(OnBackInvokedCallbackInfo.class);
+        mDispatcher.registerOnBackInvokedCallback(PRIORITY_DEFAULT, mCallback1);
+        OnBackInvokedCallbackInfo callbackInfo1 = assertSetCallbackInfo();
 
-        mDispatcher.registerOnBackInvokedCallback(
-                OnBackInvokedDispatcher.PRIORITY_DEFAULT, mCallback1);
-        mDispatcher.registerOnBackInvokedCallback(
-                OnBackInvokedDispatcher.PRIORITY_DEFAULT, mCallback2);
+        mDispatcher.registerOnBackInvokedCallback(PRIORITY_DEFAULT, mCallback2);
+        OnBackInvokedCallbackInfo callbackInfo2 = assertSetCallbackInfo();
 
-        verify(mWindowSession, times(2)).setOnBackInvokedCallbackInfo(
-                Mockito.eq(mWindow),
-                captor.capture());
-        captor.getAllValues().get(0).getCallback().onBackStarted(mBackEvent);
+        callbackInfo1.getCallback().onBackStarted(mBackEvent);
+
         waitForIdle();
         verify(mCallback1).onBackStarted(any(BackEvent.class));
         verifyZeroInteractions(mCallback2);
 
-        captor.getAllValues().get(1).getCallback().onBackStarted(mBackEvent);
+        callbackInfo2.getCallback().onBackStarted(mBackEvent);
+
         waitForIdle();
         verify(mCallback2).onBackStarted(any(BackEvent.class));
+
+        // Calls sequence: BackProgressAnimator.onBackStarted() -> BackProgressAnimator.reset() ->
+        // Spring.animateToFinalPosition(0). This causes a progress event to be fired.
+        verify(mCallback1, atMost(1)).onBackProgressed(any(BackEvent.class));
         verifyNoMoreInteractions(mCallback1);
     }
 
     @Test
     public void propagatesTopCallback_differentPriority() throws RemoteException {
-        ArgumentCaptor<OnBackInvokedCallbackInfo> captor =
-                ArgumentCaptor.forClass(OnBackInvokedCallbackInfo.class);
+        mDispatcher.registerOnBackInvokedCallback(PRIORITY_OVERLAY, mCallback1);
+        mDispatcher.registerOnBackInvokedCallback(PRIORITY_DEFAULT, mCallback2);
 
-        mDispatcher.registerOnBackInvokedCallback(
-                OnBackInvokedDispatcher.PRIORITY_OVERLAY, mCallback1);
-        mDispatcher.registerOnBackInvokedCallback(
-                OnBackInvokedDispatcher.PRIORITY_DEFAULT, mCallback2);
+        OnBackInvokedCallbackInfo callbackInfo = assertSetCallbackInfo();
 
-        verify(mWindowSession).setOnBackInvokedCallbackInfo(
-                Mockito.eq(mWindow), captor.capture());
         verifyNoMoreInteractions(mWindowSession);
-        assertEquals(captor.getValue().getPriority(), OnBackInvokedDispatcher.PRIORITY_OVERLAY);
-        captor.getValue().getCallback().onBackStarted(mBackEvent);
+        assertEquals(callbackInfo.getPriority(), PRIORITY_OVERLAY);
+
+        callbackInfo.getCallback().onBackStarted(mBackEvent);
+
         waitForIdle();
         verify(mCallback1).onBackStarted(any(BackEvent.class));
     }
 
     @Test
     public void propagatesTopCallback_withRemoval() throws RemoteException {
-        mDispatcher.registerOnBackInvokedCallback(
-                OnBackInvokedDispatcher.PRIORITY_DEFAULT, mCallback1);
-        mDispatcher.registerOnBackInvokedCallback(
-                OnBackInvokedDispatcher.PRIORITY_DEFAULT, mCallback2);
+        mDispatcher.registerOnBackInvokedCallback(PRIORITY_DEFAULT, mCallback1);
+        assertSetCallbackInfo();
 
-        reset(mWindowSession);
+        mDispatcher.registerOnBackInvokedCallback(PRIORITY_DEFAULT, mCallback2);
+        assertSetCallbackInfo();
+
         mDispatcher.unregisterOnBackInvokedCallback(mCallback1);
-        verifyZeroInteractions(mWindowSession);
+
+        waitForIdle();
+        verifyNoMoreInteractions(mWindowSession);
+        verifyNoMoreInteractions(mCallback1);
 
         mDispatcher.unregisterOnBackInvokedCallback(mCallback2);
+
+        waitForIdle();
         verify(mWindowSession).setOnBackInvokedCallbackInfo(Mockito.eq(mWindow), isNull());
     }
 
 
     @Test
     public void propagatesTopCallback_sameInstanceAddedTwice() throws RemoteException {
-        ArgumentCaptor<OnBackInvokedCallbackInfo> captor =
-                ArgumentCaptor.forClass(OnBackInvokedCallbackInfo.class);
+        mDispatcher.registerOnBackInvokedCallback(PRIORITY_OVERLAY, mCallback1);
+        assertSetCallbackInfo();
+        mDispatcher.registerOnBackInvokedCallback(PRIORITY_DEFAULT, mCallback2);
+        assertNoSetCallbackInfo();
+        mDispatcher.registerOnBackInvokedCallback(PRIORITY_DEFAULT, mCallback1);
+        assertSetCallbackInfo();
 
-        mDispatcher.registerOnBackInvokedCallback(OnBackInvokedDispatcher.PRIORITY_OVERLAY,
-                mCallback1
-        );
-        mDispatcher.registerOnBackInvokedCallback(
-                OnBackInvokedDispatcher.PRIORITY_DEFAULT, mCallback2);
-        mDispatcher.registerOnBackInvokedCallback(
-                OnBackInvokedDispatcher.PRIORITY_DEFAULT, mCallback1);
+        mDispatcher.registerOnBackInvokedCallback(PRIORITY_OVERLAY, mCallback2);
 
-        reset(mWindowSession);
-        mDispatcher.registerOnBackInvokedCallback(
-                OnBackInvokedDispatcher.PRIORITY_OVERLAY, mCallback2);
-        verify(mWindowSession).setOnBackInvokedCallbackInfo(Mockito.eq(mWindow), captor.capture());
-        captor.getValue().getCallback().onBackStarted(mBackEvent);
+        OnBackInvokedCallbackInfo lastCallbackInfo = assertSetCallbackInfo();
+
+        lastCallbackInfo.getCallback().onBackStarted(mBackEvent);
+
         waitForIdle();
         verify(mCallback2).onBackStarted(any(BackEvent.class));
     }
 
     @Test
     public void onUnregisterWhileBackInProgress_callOnBackCancelled() throws RemoteException {
-        ArgumentCaptor<OnBackInvokedCallbackInfo> captor =
-                ArgumentCaptor.forClass(OnBackInvokedCallbackInfo.class);
+        mDispatcher.registerOnBackInvokedCallback(PRIORITY_DEFAULT, mCallback1);
 
-        mDispatcher.registerOnBackInvokedCallback(
-                OnBackInvokedDispatcher.PRIORITY_DEFAULT, mCallback1);
+        OnBackInvokedCallbackInfo callbackInfo = assertSetCallbackInfo();
 
-        verify(mWindowSession).setOnBackInvokedCallbackInfo(
-                Mockito.eq(mWindow),
-                captor.capture());
-        IOnBackInvokedCallback iOnBackInvokedCallback = captor.getValue().getCallback();
-        iOnBackInvokedCallback.onBackStarted(mBackEvent);
+        callbackInfo.getCallback().onBackStarted(mBackEvent);
+
         waitForIdle();
         verify(mCallback1).onBackStarted(any(BackEvent.class));
 
         mDispatcher.unregisterOnBackInvokedCallback(mCallback1);
+
+        waitForIdle();
         verify(mCallback1).onBackCancelled();
-        verifyNoMoreInteractions(mCallback1);
+        verify(mWindowSession).setOnBackInvokedCallbackInfo(Mockito.eq(mWindow), isNull());
     }
 }
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 3b45d5d..4cedd41 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
@@ -308,7 +308,8 @@
 
             forAllTaskContainers(taskContainer -> {
                 synchronized (mLock) {
-                    final List<TaskFragmentContainer> containers = taskContainer.mContainers;
+                    final List<TaskFragmentContainer> containers =
+                            taskContainer.getTaskFragmentContainers();
                     // Clean up the TaskFragmentContainers by the z-order from the lowest.
                     for (int i = 0; i < containers.size(); i++) {
                         final TaskFragmentContainer container = containers.get(i);
@@ -611,8 +612,7 @@
             @NonNull TaskContainer taskContainer) {
         // Update all TaskFragments in the Task. Make a copy of the list since some may be
         // removed on updating.
-        final List<TaskFragmentContainer> containers =
-                new ArrayList<>(taskContainer.mContainers);
+        final List<TaskFragmentContainer> containers = taskContainer.getTaskFragmentContainers();
         for (int i = containers.size() - 1; i >= 0; i--) {
             final TaskFragmentContainer container = containers.get(i);
             // Wait until onTaskFragmentAppeared to update new container.
@@ -1331,7 +1331,8 @@
         // Check pending appeared activity first because there can be a delay for the server
         // update.
         for (int i = mTaskContainers.size() - 1; i >= 0; i--) {
-            final List<TaskFragmentContainer> containers = mTaskContainers.valueAt(i).mContainers;
+            final List<TaskFragmentContainer> containers = mTaskContainers.valueAt(i)
+                    .getTaskFragmentContainers();
             for (int j = containers.size() - 1; j >= 0; j--) {
                 final TaskFragmentContainer container = containers.get(j);
                 if (container.hasPendingAppearedActivity(activityToken)) {
@@ -1342,7 +1343,8 @@
 
         // Check appeared activity if there is no such pending appeared activity.
         for (int i = mTaskContainers.size() - 1; i >= 0; i--) {
-            final List<TaskFragmentContainer> containers = mTaskContainers.valueAt(i).mContainers;
+            final List<TaskFragmentContainer> containers = mTaskContainers.valueAt(i)
+                    .getTaskFragmentContainers();
             for (int j = containers.size() - 1; j >= 0; j--) {
                 final TaskFragmentContainer container = containers.get(j);
                 if (container.hasAppearedActivity(activityToken)) {
@@ -1472,7 +1474,7 @@
     void removeContainers(@NonNull TaskContainer taskContainer,
             @NonNull List<TaskFragmentContainer> containers) {
         // Remove all split containers that included this one
-        taskContainer.mContainers.removeAll(containers);
+        taskContainer.removeTaskFragmentContainers(containers);
         // Marked as a pending removal which will be removed after it is actually removed on the
         // server side (#onTaskFragmentVanished).
         // In this way, we can keep track of the Task bounds until we no longer have any
@@ -1497,7 +1499,9 @@
         taskContainer.removeSplitContainers(containersToRemove);
 
         // Cleanup any dependent references.
-        for (TaskFragmentContainer containerToUpdate : taskContainer.mContainers) {
+        final List<TaskFragmentContainer> taskFragmentContainers =
+                taskContainer.getTaskFragmentContainers();
+        for (TaskFragmentContainer containerToUpdate : taskFragmentContainers) {
             containerToUpdate.removeContainersToFinishOnExit(containers);
         }
     }
@@ -1536,8 +1540,9 @@
         if (taskContainer == null) {
             return null;
         }
-        for (int i = taskContainer.mContainers.size() - 1; i >= 0; i--) {
-            final TaskFragmentContainer container = taskContainer.mContainers.get(i);
+        final List<TaskFragmentContainer> containers = taskContainer.getTaskFragmentContainers();
+        for (int i = containers.size() - 1; i >= 0; i--) {
+            final TaskFragmentContainer container = containers.get(i);
             if (!container.isFinished() && (container.getRunningActivityCount() > 0
                     // We may be waiting for the top TaskFragment to become non-empty after
                     // creation. In that case, we don't want to treat the TaskFragment below it as
@@ -1933,7 +1938,8 @@
     @GuardedBy("mLock")
     TaskFragmentContainer getContainer(@NonNull IBinder fragmentToken) {
         for (int i = mTaskContainers.size() - 1; i >= 0; i--) {
-            final List<TaskFragmentContainer> containers = mTaskContainers.valueAt(i).mContainers;
+            final List<TaskFragmentContainer> containers = mTaskContainers.valueAt(i)
+                    .getTaskFragmentContainers();
             for (TaskFragmentContainer container : containers) {
                 if (container.getTaskFragmentToken().equals(fragmentToken)) {
                     return container;
@@ -2094,7 +2100,7 @@
                 }
                 for (int i = mTaskContainers.size() - 1; i >= 0; i--) {
                     final List<TaskFragmentContainer> containers = mTaskContainers.valueAt(i)
-                            .mContainers;
+                            .getTaskFragmentContainers();
                     for (int j = containers.size() - 1; j >= 0; j--) {
                         final TaskFragmentContainer container = containers.get(j);
                         if (!container.hasActivity(activityToken)
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 0626510..4580c98 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java
@@ -51,7 +51,7 @@
 
     /** Active TaskFragments in this Task. */
     @NonNull
-    final List<TaskFragmentContainer> mContainers = new ArrayList<>();
+    private final List<TaskFragmentContainer> mContainers = new ArrayList<>();
 
     /** Active split pairs in this Task. */
     @NonNull
@@ -207,9 +207,13 @@
         return false;
     }
 
+    /**
+     * Returns a list of {@link SplitContainer}. Do not modify the containers directly on the
+     * returned list. Use {@link #addSplitContainer} or {@link #removeSplitContainers} instead.
+     */
     @NonNull
     List<SplitContainer> getSplitContainers() {
-        return new ArrayList<>(mSplitContainers);
+        return mSplitContainers;
     }
 
     void addSplitContainer(@NonNull SplitContainer splitContainer) {
@@ -220,6 +224,36 @@
         mSplitContainers.removeAll(containers);
     }
 
+    void addTaskFragmentContainer(@NonNull TaskFragmentContainer taskFragmentContainer) {
+        mContainers.add(taskFragmentContainer);
+    }
+
+    void addTaskFragmentContainer(int index, @NonNull TaskFragmentContainer taskFragmentContainer) {
+        mContainers.add(index, taskFragmentContainer);
+    }
+
+    void removeTaskFragmentContainer(@NonNull TaskFragmentContainer taskFragmentContainer) {
+        mContainers.remove(taskFragmentContainer);
+    }
+
+    void removeTaskFragmentContainers(@NonNull List<TaskFragmentContainer> taskFragmentContainer) {
+        mContainers.removeAll(taskFragmentContainer);
+    }
+
+    void clearTaskFragmentContainer() {
+        mContainers.clear();
+    }
+
+    /**
+     * Returns a list of {@link TaskFragmentContainer}. Do not modify the containers directly on
+     * the returned list. Use {@link #addTaskFragmentContainer},
+     * {@link #removeTaskFragmentContainer} or other related methods instead.
+     */
+    @NonNull
+    List<TaskFragmentContainer> getTaskFragmentContainers() {
+        return mContainers;
+    }
+
     /** 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/src/androidx/window/extensions/embedding/TaskFragmentContainer.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java
index 60be9d1..61df335 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java
@@ -180,23 +180,25 @@
                 throw new IllegalArgumentException(
                         "pairedPrimaryContainer must be in the same Task");
             }
-            final int primaryIndex = taskContainer.mContainers.indexOf(pairedPrimaryContainer);
-            taskContainer.mContainers.add(primaryIndex + 1, this);
+            final int primaryIndex = taskContainer.indexOf(pairedPrimaryContainer);
+            taskContainer.addTaskFragmentContainer(primaryIndex + 1, this);
         } else if (pendingAppearedActivity != null) {
             // The TaskFragment will be positioned right above the pending appeared Activity. If any
             // existing TaskFragment is empty with pending Intent, it is likely that the Activity of
             // the pending Intent hasn't been created yet, so the new Activity should be below the
             // empty TaskFragment.
-            int i = taskContainer.mContainers.size() - 1;
+            final List<TaskFragmentContainer> containers =
+                    taskContainer.getTaskFragmentContainers();
+            int i = containers.size() - 1;
             for (; i >= 0; i--) {
-                final TaskFragmentContainer container = taskContainer.mContainers.get(i);
+                final TaskFragmentContainer container = containers.get(i);
                 if (!container.isEmpty() || container.getPendingAppearedIntent() == null) {
                     break;
                 }
             }
-            taskContainer.mContainers.add(i + 1, this);
+            taskContainer.addTaskFragmentContainer(i + 1, this);
         } else {
-            taskContainer.mContainers.add(this);
+            taskContainer.addTaskFragmentContainer(this);
         }
         if (pendingAppearedActivity != null) {
             addPendingAppearedActivity(pendingAppearedActivity);
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 82692a5..9e26472 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
@@ -183,23 +183,23 @@
         // tf2 has running activity so is active.
         final TaskFragmentContainer tf2 = mock(TaskFragmentContainer.class);
         doReturn(1).when(tf2).getRunningActivityCount();
-        taskContainer.mContainers.add(tf2);
+        taskContainer.addTaskFragmentContainer(tf2);
         // tf3 is finished so is not active.
         final TaskFragmentContainer tf3 = mock(TaskFragmentContainer.class);
         doReturn(true).when(tf3).isFinished();
         doReturn(false).when(tf3).isWaitingActivityAppear();
-        taskContainer.mContainers.add(tf3);
+        taskContainer.addTaskFragmentContainer(tf3);
         mSplitController.mTaskContainers.put(TASK_ID, taskContainer);
 
         assertWithMessage("Must return tf2 because tf3 is not active.")
                 .that(mSplitController.getTopActiveContainer(TASK_ID)).isEqualTo(tf2);
 
-        taskContainer.mContainers.remove(tf3);
+        taskContainer.removeTaskFragmentContainer(tf3);
 
         assertWithMessage("Must return tf2 because tf2 has running activity.")
                 .that(mSplitController.getTopActiveContainer(TASK_ID)).isEqualTo(tf2);
 
-        taskContainer.mContainers.remove(tf2);
+        taskContainer.removeTaskFragmentContainer(tf2);
 
         assertWithMessage("Must return tf because we are waiting for tf1 to appear.")
                 .that(mSplitController.getTopActiveContainer(TASK_ID)).isEqualTo(tf1);
@@ -365,7 +365,8 @@
         final Activity r1 = createMockActivity();
         addSplitTaskFragments(r0, r1);
         final TaskContainer taskContainer = mSplitController.getTaskContainer(TASK_ID);
-        final TaskFragmentContainer taskFragmentContainer = taskContainer.mContainers.get(0);
+        final TaskFragmentContainer taskFragmentContainer =
+                taskContainer.getTaskFragmentContainers().get(0);
         spyOn(taskContainer);
 
         // No update when the Task is invisible.
@@ -1092,7 +1093,7 @@
         verify(mTransaction).finishActivity(mActivity.getActivityToken());
         verify(mTransaction).finishActivity(secondaryActivity0.getActivityToken());
         verify(mTransaction).finishActivity(secondaryActivity1.getActivityToken());
-        assertTrue(taskContainer.mContainers.isEmpty());
+        assertTrue(taskContainer.getTaskFragmentContainers().isEmpty());
         assertTrue(taskContainer.getSplitContainers().isEmpty());
     }
 
@@ -1365,15 +1366,13 @@
         TaskFragmentContainer tf = mSplitController.newContainer(mActivity, TASK_ID);
         tf.setInfo(mTransaction, createMockTaskFragmentInfo(tf, mActivity));
 
-        List<TaskFragmentContainer> containers = mSplitController.mTaskContainers.get(TASK_ID)
-                .mContainers;
-
-        assertEquals(containers.get(0), tf);
+        final TaskContainer taskContainer = mSplitController.mTaskContainers.get(TASK_ID);
+        assertEquals(taskContainer.getTaskFragmentContainers().get(0), tf);
 
         mSplitController.finishActivityStacks(Collections.singleton(tf.getTaskFragmentToken()));
 
         verify(mSplitPresenter).deleteTaskFragment(any(), eq(tf.getTaskFragmentToken()));
-        assertTrue(containers.isEmpty());
+        assertTrue(taskContainer.getTaskFragmentContainers().isEmpty());
     }
 
     @Test
@@ -1383,10 +1382,8 @@
         bottomTf.setInfo(mTransaction, createMockTaskFragmentInfo(bottomTf, mActivity));
         topTf.setInfo(mTransaction, createMockTaskFragmentInfo(topTf, createMockActivity()));
 
-        List<TaskFragmentContainer> containers = mSplitController.mTaskContainers.get(TASK_ID)
-                .mContainers;
-
-        assertEquals(containers.size(), 2);
+        final TaskContainer taskContainer = mSplitController.mTaskContainers.get(TASK_ID);
+        assertEquals(taskContainer.getTaskFragmentContainers().size(), 2);
 
         Set<IBinder> activityStackTokens = new ArraySet<>(new IBinder[]{
                 topTf.getTaskFragmentToken(), bottomTf.getTaskFragmentToken()});
@@ -1405,7 +1402,7 @@
                         + "regardless of the order in ActivityStack set",
                 topTf.getTaskFragmentToken(), fragmentTokens.get(1));
 
-        assertTrue(containers.isEmpty());
+        assertTrue(taskContainer.getTaskFragmentContainers().isEmpty());
     }
 
     @Test
diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskContainerTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskContainerTest.java
index 13e7092..11af1d1 100644
--- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskContainerTest.java
+++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskContainerTest.java
@@ -127,7 +127,7 @@
         assertFalse(taskContainer.isEmpty());
 
         taskContainer.mFinishedContainer.add(tf.getTaskFragmentToken());
-        taskContainer.mContainers.clear();
+        taskContainer.clearTaskFragmentContainer();
 
         assertFalse(taskContainer.isEmpty());
     }
@@ -152,13 +152,13 @@
         assertNull(taskContainer.getTopNonFinishingActivity());
 
         final TaskFragmentContainer tf0 = mock(TaskFragmentContainer.class);
-        taskContainer.mContainers.add(tf0);
+        taskContainer.addTaskFragmentContainer(tf0);
         final Activity activity0 = mock(Activity.class);
         doReturn(activity0).when(tf0).getTopNonFinishingActivity();
         assertEquals(activity0, taskContainer.getTopNonFinishingActivity());
 
         final TaskFragmentContainer tf1 = mock(TaskFragmentContainer.class);
-        taskContainer.mContainers.add(tf1);
+        taskContainer.addTaskFragmentContainer(tf1);
         assertEquals(activity0, taskContainer.getTopNonFinishingActivity());
 
         final Activity activity1 = mock(Activity.class);
diff --git a/libs/WindowManager/Shell/res/drawable/bubble_manage_menu_section.xml b/libs/WindowManager/Shell/res/drawable/bubble_manage_menu_section.xml
new file mode 100644
index 0000000..d99d64d
--- /dev/null
+++ b/libs/WindowManager/Shell/res/drawable/bubble_manage_menu_section.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2020 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:state_pressed="true">
+        <ripple android:color="#99999999">
+            <item android:drawable="@drawable/bubble_manage_menu_bg" />
+        </ripple>
+    </item>
+    <item android:drawable="@drawable/bubble_manage_menu_bg" />
+</selector>
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/res/drawable/ic_expand_less.xml b/libs/WindowManager/Shell/res/drawable/ic_expand_less.xml
new file mode 100644
index 0000000..f450846
--- /dev/null
+++ b/libs/WindowManager/Shell/res/drawable/ic_expand_less.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2023 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License
+  -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportWidth="24"
+    android:viewportHeight="24">
+  <path
+      android:pathData="M18.59,16.41L20,15L12,7L4,15L5.41,16.41L12,9.83"
+      android:fillColor="#5F6368"/>
+</vector>
diff --git a/libs/WindowManager/Shell/res/layout/bubble_bar_menu_item.xml b/libs/WindowManager/Shell/res/layout/bubble_bar_menu_item.xml
new file mode 100644
index 0000000..ddcd5c6
--- /dev/null
+++ b/libs/WindowManager/Shell/res/layout/bubble_bar_menu_item.xml
@@ -0,0 +1,41 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2023 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License
+  -->
+<com.android.wm.shell.bubbles.bar.BubbleBarMenuItemView
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:orientation="horizontal"
+    android:layout_width="match_parent"
+    android:layout_height="@dimen/bubble_bar_manage_menu_item_height"
+    android:gravity="center_vertical"
+    android:paddingStart="@dimen/bubble_menu_padding"
+    android:paddingEnd="@dimen/bubble_menu_padding"
+    android:background="@drawable/bubble_manage_menu_row">
+
+    <ImageView
+        android:id="@+id/bubble_bar_menu_item_icon"
+        android:layout_width="@dimen/bubble_bar_manage_menu_item_icon_size"
+        android:layout_height="@dimen/bubble_bar_manage_menu_item_icon_size"
+        android:contentDescription="@null"/>
+
+    <TextView
+        android:id="@+id/bubble_bar_menu_item_title"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_marginStart="16dp"
+        android:textColor="?android:attr/textColorPrimary"
+        android:textAppearance="@*android:style/TextAppearance.DeviceDefault" />
+
+</com.android.wm.shell.bubbles.bar.BubbleBarMenuItemView>
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/res/layout/bubble_bar_menu_view.xml b/libs/WindowManager/Shell/res/layout/bubble_bar_menu_view.xml
new file mode 100644
index 0000000..82e5aee
--- /dev/null
+++ b/libs/WindowManager/Shell/res/layout/bubble_bar_menu_view.xml
@@ -0,0 +1,77 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2023 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License
+  -->
+<com.android.wm.shell.bubbles.bar.BubbleBarMenuView
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    android:layout_width="wrap_content"
+    android:layout_height="wrap_content"
+    android:layout_gravity="center_horizontal"
+    android:minWidth="@dimen/bubble_bar_manage_menu_min_width"
+    android:orientation="vertical"
+    android:elevation="@dimen/bubble_manage_menu_elevation"
+    android:paddingTop="@dimen/bubble_bar_manage_menu_padding_top"
+    android:paddingHorizontal="@dimen/bubble_bar_manage_menu_padding"
+    android:paddingBottom="@dimen/bubble_bar_manage_menu_padding"
+    android:clipToPadding="false">
+
+    <LinearLayout
+        android:id="@+id/bubble_bar_manage_menu_bubble_section"
+        android:layout_width="match_parent"
+        android:layout_height="@dimen/bubble_bar_manage_menu_item_height"
+        android:orientation="horizontal"
+        android:gravity="center_vertical"
+        android:paddingStart="14dp"
+        android:paddingEnd="12dp"
+        android:background="@drawable/bubble_manage_menu_section"
+        android:elevation="@dimen/bubble_manage_menu_elevation">
+
+        <ImageView
+            android:id="@+id/bubble_bar_manage_menu_bubble_icon"
+            android:layout_width="@dimen/bubble_menu_icon_size"
+            android:layout_height="@dimen/bubble_menu_icon_size"
+            android:contentDescription="@null" />
+
+        <TextView
+            android:id="@+id/bubble_bar_manage_menu_bubble_title"
+            android:layout_width="0dp"
+            android:layout_height="wrap_content"
+            android:layout_marginStart="8dp"
+            android:layout_weight="1"
+            android:textColor="?android:attr/textColorPrimary"
+            android:textAppearance="@*android:style/TextAppearance.DeviceDefault" />
+
+        <ImageView
+            android:id="@+id/bubble_bar_manage_menu_dismiss_icon"
+            android:layout_width="@dimen/bubble_bar_manage_menu_dismiss_icon_size"
+            android:layout_height="@dimen/bubble_bar_manage_menu_dismiss_icon_size"
+            android:layout_marginStart="8dp"
+            android:contentDescription="@null"
+            android:src="@drawable/ic_expand_less"
+            app:tint="?android:attr/textColorPrimary" />
+
+    </LinearLayout>
+
+    <LinearLayout
+        android:id="@+id/bubble_bar_manage_menu_actions_section"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:orientation="vertical"
+        android:layout_marginTop="@dimen/bubble_bar_manage_menu_section_spacing"
+        android:background="@drawable/bubble_manage_menu_bg"
+        android:elevation="@dimen/bubble_manage_menu_elevation" />
+
+</com.android.wm.shell.bubbles.bar.BubbleBarMenuView>
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/res/values/colors.xml b/libs/WindowManager/Shell/res/values/colors.xml
index 6f8a766..f2a0785 100644
--- a/libs/WindowManager/Shell/res/values/colors.xml
+++ b/libs/WindowManager/Shell/res/values/colors.xml
@@ -32,6 +32,7 @@
     <color name="bubbles_icon_tint">@color/GM2_grey_700</color>
     <color name="bubble_bar_expanded_view_handle_light">#EBffffff</color>
     <color name="bubble_bar_expanded_view_handle_dark">#99000000</color>
+    <color name="bubble_bar_expanded_view_menu_close">#DC362E</color>
 
     <!-- PiP -->
     <color name="pip_custom_close_bg">#D93025</color>
diff --git a/libs/WindowManager/Shell/res/values/dimen.xml b/libs/WindowManager/Shell/res/values/dimen.xml
index aa05179..2141259 100644
--- a/libs/WindowManager/Shell/res/values/dimen.xml
+++ b/libs/WindowManager/Shell/res/values/dimen.xml
@@ -234,6 +234,20 @@
     <dimen name="bubble_bar_expanded_view_handle_width">128dp</dimen>
     <!-- The height of the drag handle shown along with a bubble bar expanded view. -->
     <dimen name="bubble_bar_expanded_view_handle_height">4dp</dimen>
+    <!-- Minimum width of the bubble bar manage menu. -->
+    <dimen name="bubble_bar_manage_menu_min_width">200dp</dimen>
+    <!-- Size of the dismiss icon in the bubble bar manage menu. -->
+    <dimen name="bubble_bar_manage_menu_dismiss_icon_size">16dp</dimen>
+    <!-- Padding of the bubble bar manage menu, provides space for menu shadows -->
+    <dimen name="bubble_bar_manage_menu_padding">8dp</dimen>
+    <!-- Top padding of the bubble bar manage menu -->
+    <dimen name="bubble_bar_manage_menu_padding_top">2dp</dimen>
+    <!-- Spacing between sections of the bubble bar manage menu -->
+    <dimen name="bubble_bar_manage_menu_section_spacing">2dp</dimen>
+    <!-- Height of an item in the bubble bar manage menu. -->
+    <dimen name="bubble_bar_manage_menu_item_height">52dp</dimen>
+    <!-- Size of the icons in the bubble bar manage menu. -->
+    <dimen name="bubble_bar_manage_menu_item_icon_size">20dp</dimen>
 
     <!-- Bottom and end margin for compat buttons. -->
     <dimen name="compat_button_margin">24dp</dimen>
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java
index 102f2cb..504839f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java
@@ -855,7 +855,8 @@
         return mIsAppBubble;
     }
 
-    Intent getSettingsIntent(final Context context) {
+    /** Creates open app settings intent */
+    public Intent getSettingsIntent(final Context context) {
         final Intent intent = new Intent(Settings.ACTION_APP_NOTIFICATION_BUBBLE_SETTINGS);
         intent.putExtra(Settings.EXTRA_APP_PACKAGE, getPackageName());
         final int uid = getUid(context);
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 988ad8c..39338e5 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
@@ -720,6 +720,7 @@
             // TODO(b/273312602): consider foldables where we do need a stack view when folded
             if (mLayerView == null) {
                 mLayerView = new BubbleBarLayerView(mContext, this);
+                mLayerView.setUnBubbleConversationCallback(mSysuiProxy::onUnbubbleConversation);
             }
         } else {
             if (mStackView == null) {
@@ -1221,6 +1222,13 @@
     }
 
     /**
+     * Dismiss bubble if it exists and remove it from the stack
+     */
+    public void dismissBubble(Bubble bubble, @Bubbles.DismissReason int reason) {
+        mBubbleData.dismissBubbleWithKey(bubble.getKey(), reason);
+    }
+
+    /**
      * Performs a screenshot that may exclude the bubble layer, if one is present. The screenshot
      * can be access via the supplied {@link ScreenshotSync#get()} asynchronously.
      */
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleViewInfoTask.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleViewInfoTask.java
index 8ab9841..3a46797 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleViewInfoTask.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleViewInfoTask.java
@@ -192,6 +192,11 @@
             }
 
             info.rawBadgeBitmap = iconFactory.getBadgeBitmap(badgedIcon, false).icon;
+            float[] bubbleBitmapScale = new float[1];
+            info.bubbleBitmap = iconFactory.getBubbleBitmap(
+                    iconFactory.getBubbleDrawable(c, info.shortcutInfo,
+                            b.getIcon()), bubbleBitmapScale);
+
 
             return info;
         }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedView.java
index da1a557..32ed102 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedView.java
@@ -23,6 +23,7 @@
 import android.content.res.TypedArray;
 import android.graphics.Color;
 import android.graphics.Outline;
+import android.graphics.Rect;
 import android.util.AttributeSet;
 import android.view.View;
 import android.view.ViewOutlineProvider;
@@ -33,8 +34,12 @@
 import com.android.wm.shell.bubbles.Bubble;
 import com.android.wm.shell.bubbles.BubbleController;
 import com.android.wm.shell.bubbles.BubbleTaskViewHelper;
+import com.android.wm.shell.bubbles.Bubbles;
 import com.android.wm.shell.taskview.TaskView;
 
+import java.util.function.Consumer;
+import java.util.function.Supplier;
+
 /**
  * Expanded view of a bubble when it's part of the bubble bar.
  *
@@ -47,6 +52,9 @@
 
     private BubbleController mController;
     private BubbleTaskViewHelper mBubbleTaskViewHelper;
+    private BubbleBarMenuViewController mMenuViewController;
+    private @Nullable Supplier<Rect> mLayerBoundsSupplier;
+    private @Nullable Consumer<String> mUnBubbleConversationCallback;
 
     private BubbleBarHandleView mHandleView = new BubbleBarHandleView(getContext());
     private @Nullable TaskView mTaskView;
@@ -98,6 +106,13 @@
         });
     }
 
+    @Override
+    protected void onDetachedFromWindow() {
+        super.onDetachedFromWindow();
+        // Hide manage menu when view disappears
+        mMenuViewController.hideMenu(false /* animated */);
+    }
+
     /** Set the BubbleController on the view, must be called before doing anything else. */
     public void initialize(BubbleController controller) {
         mController = controller;
@@ -108,6 +123,36 @@
         addView(mTaskView);
         mTaskView.setEnableSurfaceClipping(true);
         mTaskView.setCornerRadius(mCornerRadius);
+        mMenuViewController = new BubbleBarMenuViewController(mContext, this);
+        mMenuViewController.setListener(new BubbleBarMenuViewController.Listener() {
+            @Override
+            public void onMenuVisibilityChanged(boolean visible) {
+                if (mTaskView == null || mLayerBoundsSupplier == null) return;
+                // Updates the obscured touchable region for the task surface.
+                mTaskView.setObscuredTouchRect(visible ? mLayerBoundsSupplier.get() : null);
+            }
+
+            @Override
+            public void onUnBubbleConversation(Bubble bubble) {
+                if (mUnBubbleConversationCallback != null) {
+                    mUnBubbleConversationCallback.accept(bubble.getKey());
+                }
+            }
+
+            @Override
+            public void onOpenAppSettings(Bubble bubble) {
+                mController.collapseStack();
+                mContext.startActivityAsUser(bubble.getSettingsIntent(mContext), bubble.getUser());
+            }
+
+            @Override
+            public void onDismissBubble(Bubble bubble) {
+                mController.dismissBubble(bubble, Bubbles.DISMISS_USER_REMOVED);
+            }
+        });
+        mHandleView.setOnClickListener(view -> {
+            mMenuViewController.showMenu(true /* animated */);
+        });
     }
 
     // TODO (b/275087636): call this when theme/config changes
@@ -183,11 +228,13 @@
             }
             mBubbleTaskViewHelper.cleanUpTaskView();
         }
+        mMenuViewController.hideMenu(false /* animated */);
     }
 
-    /** Updates the bubble shown in this task view. */
+    /** Updates the bubble shown in the expanded view. */
     public void update(Bubble bubble) {
         mBubbleTaskViewHelper.update(bubble);
+        mMenuViewController.updateMenu(bubble);
     }
 
     /** The task id of the activity shown in the task view, if it exists. */
@@ -195,6 +242,17 @@
         return mBubbleTaskViewHelper != null ? mBubbleTaskViewHelper.getTaskId() : INVALID_TASK_ID;
     }
 
+    /** Sets layer bounds supplier used for obscured touchable region of task view */
+    void setLayerBoundsSupplier(@Nullable Supplier<Rect> supplier) {
+        mLayerBoundsSupplier = supplier;
+    }
+
+    /** Sets the function to call to un-bubble the given conversation. */
+    public void setUnBubbleConversationCallback(
+            @Nullable Consumer<String> unBubbleConversationCallback) {
+        mUnBubbleConversationCallback = unBubbleConversationCallback;
+    }
+
     /**
      * Call when the location or size of the view has changed to update TaskView.
      */
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarHandleView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarHandleView.java
index e121aa4..ce26bc0 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarHandleView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarHandleView.java
@@ -20,7 +20,6 @@
 import android.animation.ObjectAnimator;
 import android.annotation.Nullable;
 import android.content.Context;
-import android.content.res.Resources;
 import android.graphics.Outline;
 import android.graphics.Rect;
 import android.util.AttributeSet;
@@ -38,35 +37,35 @@
 public class BubbleBarHandleView extends View {
     private static final long COLOR_CHANGE_DURATION = 120;
 
-    private final int mHandleWidth;
-    private final int mHandleHeight;
-    private final @ColorInt int mHandleLightColor;
-    private final @ColorInt int mHandleDarkColor;
+    private int mHandleWidth;
+    private int mHandleHeight;
+    private @ColorInt int mHandleLightColor;
+    private @ColorInt int mHandleDarkColor;
     private @Nullable ObjectAnimator mColorChangeAnim;
 
     public BubbleBarHandleView(Context context) {
-        this(context, null);
+        this(context, null /* attrs */);
     }
 
     public BubbleBarHandleView(Context context, AttributeSet attrs) {
-        this(context, attrs, 0);
+        this(context, attrs, 0 /* defStyleAttr */);
     }
 
     public BubbleBarHandleView(Context context, AttributeSet attrs, int defStyleAttr) {
-        this(context, attrs, defStyleAttr, 0);
+        this(context, attrs, defStyleAttr, 0 /* defStyleRes */);
     }
 
     public BubbleBarHandleView(Context context, AttributeSet attrs, int defStyleAttr,
             int defStyleRes) {
         super(context, attrs, defStyleAttr, defStyleRes);
-        Resources resources = context.getResources();
-        mHandleWidth = resources.getDimensionPixelSize(
+
+        mHandleWidth = getResources().getDimensionPixelSize(
                 R.dimen.bubble_bar_expanded_view_handle_width);
-        mHandleHeight = resources.getDimensionPixelSize(
+        mHandleHeight = getResources().getDimensionPixelSize(
                 R.dimen.bubble_bar_expanded_view_handle_height);
-        mHandleLightColor = ContextCompat.getColor(context,
+        mHandleLightColor = ContextCompat.getColor(getContext(),
                 R.color.bubble_bar_expanded_view_handle_light);
-        mHandleDarkColor = ContextCompat.getColor(context,
+        mHandleDarkColor = ContextCompat.getColor(getContext(),
                 R.color.bubble_bar_expanded_view_handle_dark);
 
         setClipToOutline(true);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java
index b1a725b..bc622e7 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java
@@ -32,6 +32,8 @@
 import com.android.wm.shell.bubbles.BubblePositioner;
 import com.android.wm.shell.bubbles.BubbleViewProvider;
 
+import java.util.function.Consumer;
+
 /**
  * Similar to {@link com.android.wm.shell.bubbles.BubbleStackView}, this view is added to window
  * manager to display bubbles. However, it is only used when bubbles are being displayed in
@@ -53,6 +55,7 @@
     @Nullable
     private BubbleViewProvider mExpandedBubble;
     private BubbleBarExpandedView mExpandedView;
+    private @Nullable Consumer<String> mUnBubbleConversationCallback;
 
     // TODO(b/273310265) - currently the view is always on the right, need to update for RTL.
     /** Whether the expanded view is displaying on the left of the screen or not. */
@@ -146,6 +149,13 @@
             final int width = mPositioner.getExpandedViewWidthForBubbleBar();
             final int height = mPositioner.getExpandedViewHeightForBubbleBar();
             mExpandedView.setVisibility(GONE);
+            mExpandedView.setUnBubbleConversationCallback(mUnBubbleConversationCallback);
+            mExpandedView.setLayerBoundsSupplier(() -> new Rect(0, 0, getWidth(), getHeight()));
+            mExpandedView.setUnBubbleConversationCallback(bubbleKey -> {
+                if (mUnBubbleConversationCallback != null) {
+                    mUnBubbleConversationCallback.accept(bubbleKey);
+                }
+            });
             addView(mExpandedView, new FrameLayout.LayoutParams(width, height));
         }
 
@@ -165,6 +175,12 @@
         showScrim(false);
     }
 
+    /** Sets the function to call to un-bubble the given conversation. */
+    public void setUnBubbleConversationCallback(
+            @Nullable Consumer<String> unBubbleConversationCallback) {
+        mUnBubbleConversationCallback = unBubbleConversationCallback;
+    }
+
     /** Updates the expanded view size and position. */
     private void updateExpandedView() {
         if (mExpandedView == null) return;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarMenuItemView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarMenuItemView.java
new file mode 100644
index 0000000..00b9777
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarMenuItemView.java
@@ -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.wm.shell.bubbles.bar;
+
+import android.annotation.ColorInt;
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.graphics.Color;
+import android.graphics.drawable.Icon;
+import android.util.AttributeSet;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+import com.android.wm.shell.R;
+
+/**
+ * Bubble bar expanded view menu item view to display menu action details
+ */
+public class BubbleBarMenuItemView extends LinearLayout {
+    private ImageView mImageView;
+    private TextView mTextView;
+
+    public BubbleBarMenuItemView(Context context) {
+        this(context, null /* attrs */);
+    }
+
+    public BubbleBarMenuItemView(Context context, AttributeSet attrs) {
+        this(context, attrs, 0 /* defStyleAttr */);
+    }
+
+    public BubbleBarMenuItemView(Context context, AttributeSet attrs, int defStyleAttr) {
+        this(context, attrs, defStyleAttr, 0 /* defStyleRes */);
+    }
+
+    public BubbleBarMenuItemView(Context context, AttributeSet attrs, int defStyleAttr,
+            int defStyleRes) {
+        super(context, attrs, defStyleAttr, defStyleRes);
+    }
+
+    @Override
+    protected void onFinishInflate() {
+        super.onFinishInflate();
+        mImageView = findViewById(R.id.bubble_bar_menu_item_icon);
+        mTextView = findViewById(R.id.bubble_bar_menu_item_title);
+    }
+
+    /**
+     * Update menu item with the details and tint color
+     */
+    void update(Icon icon, String title, @ColorInt int tint) {
+        if (tint == Color.TRANSPARENT) {
+            final TypedArray typedArray = getContext().obtainStyledAttributes(
+                    new int[]{android.R.attr.textColorPrimary});
+            mTextView.setTextColor(typedArray.getColor(0, Color.BLACK));
+        } else {
+            icon.setTint(tint);
+            mTextView.setTextColor(tint);
+        }
+
+        mImageView.setImageIcon(icon);
+        mTextView.setText(title);
+    }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarMenuView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarMenuView.java
new file mode 100644
index 0000000..211fe0d
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarMenuView.java
@@ -0,0 +1,145 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.wm.shell.bubbles.bar;
+
+import android.annotation.ColorInt;
+import android.content.Context;
+import android.graphics.Color;
+import android.graphics.drawable.Icon;
+import android.util.AttributeSet;
+import android.view.LayoutInflater;
+import android.view.ViewGroup;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+import com.android.wm.shell.R;
+import com.android.wm.shell.bubbles.Bubble;
+
+import java.util.ArrayList;
+
+/**
+ * Bubble bar expanded view menu
+ */
+public class BubbleBarMenuView extends LinearLayout {
+    private ViewGroup mBubbleSectionView;
+    private ViewGroup mActionsSectionView;
+    private ImageView mBubbleIconView;
+    private TextView mBubbleTitleView;
+
+    public BubbleBarMenuView(Context context) {
+        this(context, null /* attrs */);
+    }
+
+    public BubbleBarMenuView(Context context, AttributeSet attrs) {
+        this(context, attrs, 0 /* defStyleAttr */);
+    }
+
+    public BubbleBarMenuView(Context context, AttributeSet attrs, int defStyleAttr) {
+        this(context, attrs, defStyleAttr, 0 /* defStyleRes */);
+    }
+
+    public BubbleBarMenuView(Context context, AttributeSet attrs, int defStyleAttr,
+            int defStyleRes) {
+        super(context, attrs, defStyleAttr, defStyleRes);
+    }
+
+    @Override
+    protected void onFinishInflate() {
+        super.onFinishInflate();
+        mBubbleSectionView = findViewById(R.id.bubble_bar_manage_menu_bubble_section);
+        mActionsSectionView = findViewById(R.id.bubble_bar_manage_menu_actions_section);
+        mBubbleIconView = findViewById(R.id.bubble_bar_manage_menu_bubble_icon);
+        mBubbleTitleView = findViewById(R.id.bubble_bar_manage_menu_bubble_title);
+    }
+
+    /** Update menu details with bubble info */
+    void updateInfo(Bubble bubble) {
+        if (bubble.getIcon() != null) {
+            mBubbleIconView.setImageIcon(bubble.getIcon());
+        } else {
+            mBubbleIconView.setImageBitmap(bubble.getBubbleIcon());
+        }
+        mBubbleTitleView.setText(bubble.getTitle());
+    }
+
+    /**
+     * Update menu action items views
+     * @param actions used to populate menu item views
+     */
+    void updateActions(ArrayList<MenuAction> actions) {
+        mActionsSectionView.removeAllViews();
+        LayoutInflater inflater = LayoutInflater.from(mContext);
+
+        for (MenuAction action : actions) {
+            BubbleBarMenuItemView itemView = (BubbleBarMenuItemView) inflater.inflate(
+                    R.layout.bubble_bar_menu_item, mActionsSectionView, false);
+            itemView.update(action.mIcon, action.mTitle, action.mTint);
+            itemView.setOnClickListener(action.mOnClick);
+            mActionsSectionView.addView(itemView);
+        }
+    }
+
+    /** Sets on close menu listener */
+    void setOnCloseListener(Runnable onClose) {
+        mBubbleSectionView.setOnClickListener(view -> {
+            onClose.run();
+        });
+    }
+
+    /**
+     * Overridden to proxy to section views alpha.
+     * @implNote
+     * If animate alpha on the parent (menu container) view, section view shadows get distorted.
+     * To prevent distortion and artifacts alpha changes applied directly on the section views.
+     */
+    @Override
+    public void setAlpha(float alpha) {
+        mBubbleSectionView.setAlpha(alpha);
+        mActionsSectionView.setAlpha(alpha);
+    }
+
+    /**
+     * Overridden to proxy section view alpha value.
+     * @implNote
+     * The assumption is that both section views have the same alpha value
+     */
+    @Override
+    public float getAlpha() {
+        return mBubbleSectionView.getAlpha();
+    }
+
+    /**
+     * Menu action details used to create menu items
+     */
+    static class MenuAction {
+        private Icon mIcon;
+        private @ColorInt int mTint;
+        private String mTitle;
+        private OnClickListener mOnClick;
+
+        MenuAction(Icon icon, String title, OnClickListener onClick) {
+            this(icon, title, Color.TRANSPARENT, onClick);
+        }
+
+        MenuAction(Icon icon, String title, @ColorInt int tint, OnClickListener onClick) {
+            this.mIcon = icon;
+            this.mTitle = title;
+            this.mTint = tint;
+            this.mOnClick = onClick;
+        }
+    }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarMenuViewController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarMenuViewController.java
new file mode 100644
index 0000000..8be140c
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarMenuViewController.java
@@ -0,0 +1,242 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.wm.shell.bubbles.bar;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.drawable.Icon;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+
+import androidx.core.content.ContextCompat;
+import androidx.dynamicanimation.animation.DynamicAnimation;
+import androidx.dynamicanimation.animation.SpringForce;
+
+import com.android.wm.shell.R;
+import com.android.wm.shell.animation.PhysicsAnimator;
+import com.android.wm.shell.bubbles.Bubble;
+
+import java.util.ArrayList;
+
+/**
+ * Manages bubble bar expanded view menu presentation and animations
+ */
+class BubbleBarMenuViewController {
+    private static final float MENU_INITIAL_SCALE = 0.5f;
+    private final Context mContext;
+    private final ViewGroup mRootView;
+    private @Nullable Listener mListener;
+    private @Nullable Bubble mBubble;
+    private @Nullable BubbleBarMenuView mMenuView;
+    /** A transparent view used to intercept touches to collapse menu when presented */
+    private @Nullable View mScrimView;
+    private @Nullable PhysicsAnimator<BubbleBarMenuView> mMenuAnimator;
+    private PhysicsAnimator.SpringConfig mMenuSpringConfig;
+
+    BubbleBarMenuViewController(Context context, ViewGroup rootView) {
+        mContext = context;
+        mRootView = rootView;
+        mMenuSpringConfig = new PhysicsAnimator.SpringConfig(
+                SpringForce.STIFFNESS_MEDIUM, SpringForce.DAMPING_RATIO_LOW_BOUNCY);
+    }
+
+    /** Sets menu actions listener */
+    void setListener(@Nullable Listener listener) {
+        mListener = listener;
+    }
+
+    /** Update menu with bubble */
+    void updateMenu(@NonNull Bubble bubble) {
+        mBubble = bubble;
+    }
+
+    /**
+     * Show bubble bar expanded view menu
+     * @param animated if should animate transition
+     */
+    void showMenu(boolean animated) {
+        if (mMenuView == null || mScrimView == null) {
+            setupMenu();
+        }
+        cancelAnimations();
+        mMenuView.setVisibility(View.VISIBLE);
+        mScrimView.setVisibility(View.VISIBLE);
+        Runnable endActions = () -> {
+            mMenuView.getChildAt(0).requestAccessibilityFocus();
+            if (mListener != null) {
+                mListener.onMenuVisibilityChanged(true /* isShown */);
+            }
+        };
+        if (animated) {
+            animateTransition(true /* show */, endActions);
+        } else {
+            endActions.run();
+        }
+    }
+
+    /**
+     * Hide bubble bar expanded view menu
+     * @param animated if should animate transition
+     */
+    void hideMenu(boolean animated) {
+        if (mMenuView == null || mScrimView == null) return;
+        cancelAnimations();
+        Runnable endActions = () -> {
+            mMenuView.setVisibility(View.GONE);
+            mScrimView.setVisibility(View.GONE);
+            if (mListener != null) {
+                mListener.onMenuVisibilityChanged(false /* isShown */);
+            }
+        };
+        if (animated) {
+            animateTransition(false /* show */, endActions);
+        } else {
+            endActions.run();
+        }
+    }
+
+    /**
+     * Animate show/hide menu transition
+     * @param show if should show or hide the menu
+     * @param endActions will be called when animation ends
+     */
+    private void animateTransition(boolean show, Runnable endActions) {
+        if (mMenuView == null) return;
+        mMenuAnimator = PhysicsAnimator.getInstance(mMenuView);
+        mMenuAnimator.setDefaultSpringConfig(mMenuSpringConfig);
+        mMenuAnimator
+                .spring(DynamicAnimation.ALPHA, show ? 1f : 0f)
+                .spring(DynamicAnimation.SCALE_Y, show ? 1f : MENU_INITIAL_SCALE)
+                .withEndActions(() -> {
+                    mMenuAnimator = null;
+                    endActions.run();
+                })
+                .start();
+    }
+
+    /** Cancel running animations */
+    private void cancelAnimations() {
+        if (mMenuAnimator != null) {
+            mMenuAnimator.cancel();
+            mMenuAnimator = null;
+        }
+    }
+
+    /** Sets up and inflate menu views */
+    private void setupMenu() {
+        // Menu view setup
+        mMenuView = (BubbleBarMenuView) LayoutInflater.from(mContext).inflate(
+                R.layout.bubble_bar_menu_view, mRootView, false);
+        mMenuView.setAlpha(0f);
+        mMenuView.setPivotY(0f);
+        mMenuView.setScaleY(MENU_INITIAL_SCALE);
+        mMenuView.setOnCloseListener(() -> hideMenu(true  /* animated */));
+        if (mBubble != null) {
+            mMenuView.updateInfo(mBubble);
+            mMenuView.updateActions(createMenuActions(mBubble));
+        }
+        // Scrim view setup
+        mScrimView = new View(mContext);
+        mScrimView.setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_NO);
+        mScrimView.setOnClickListener(view -> hideMenu(true  /* animated */));
+        // Attach to root view
+        mRootView.addView(mScrimView);
+        mRootView.addView(mMenuView);
+    }
+
+    /**
+     * Creates menu actions to populate menu view
+     * @param bubble used to create actions depending on bubble type
+     */
+    private ArrayList<BubbleBarMenuView.MenuAction> createMenuActions(Bubble bubble) {
+        ArrayList<BubbleBarMenuView.MenuAction> menuActions = new ArrayList<>();
+        Resources resources = mContext.getResources();
+
+        if (bubble.isConversation()) {
+            // Don't bubble conversation action
+            menuActions.add(new BubbleBarMenuView.MenuAction(
+                    Icon.createWithResource(mContext, R.drawable.bubble_ic_stop_bubble),
+                    resources.getString(R.string.bubbles_dont_bubble_conversation),
+                    view -> {
+                        hideMenu(true /* animated */);
+                        if (mListener != null) {
+                            mListener.onUnBubbleConversation(bubble);
+                        }
+                    }
+            ));
+            // Open settings action
+            Icon appIcon = bubble.getRawAppBadge() != null ? Icon.createWithBitmap(
+                    bubble.getRawAppBadge()) : null;
+            menuActions.add(new BubbleBarMenuView.MenuAction(
+                    appIcon,
+                    resources.getString(R.string.bubbles_app_settings, bubble.getAppName()),
+                    view -> {
+                        hideMenu(true /* animated */);
+                        if (mListener != null) {
+                            mListener.onOpenAppSettings(bubble);
+                        }
+                    }
+            ));
+        }
+
+        // Dismiss bubble action
+        menuActions.add(new BubbleBarMenuView.MenuAction(
+                Icon.createWithResource(resources, R.drawable.ic_remove_no_shadow),
+                resources.getString(R.string.bubble_dismiss_text),
+                ContextCompat.getColor(mContext, R.color.bubble_bar_expanded_view_menu_close),
+                view -> {
+                    hideMenu(true /* animated */);
+                    if (mListener != null) {
+                        mListener.onDismissBubble(bubble);
+                    }
+                }
+        ));
+
+        return menuActions;
+    }
+
+    /**
+     * Bubble bar expanded view menu actions listener
+     */
+    interface Listener {
+        /**
+         * Called when manage menu is shown/hidden
+         * If animated will be called when animation ends
+         */
+        void onMenuVisibilityChanged(boolean visible);
+
+        /**
+         * Un-bubbles conversation and removes the bubble from the stack
+         * This conversation will not be bubbled with new messages
+         * @see com.android.wm.shell.bubbles.BubbleController
+         */
+        void onUnBubbleConversation(Bubble bubble);
+
+        /**
+         * Launches app notification bubble settings for the bubble with intent created in:
+         * {@code Bubble.getSettingsIntent}
+         */
+        void onOpenAppSettings(Bubble bubble);
+
+        /**
+         * Dismiss bubble and remove it from the bubble stack
+         */
+        void onDismissBubble(Bubble bubble);
+    }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
index bc0b71c..2dbccac 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
@@ -542,9 +542,12 @@
             Optional<PipTouchHandler> pipTouchHandlerOptional,
             Optional<RecentsTransitionHandler> recentsTransitionHandler,
             KeyguardTransitionHandler keyguardTransitionHandler,
+            Optional<DesktopModeController> desktopModeController,
+            Optional<DesktopTasksController> desktopTasksController,
             Transitions transitions) {
         return new DefaultMixedHandler(shellInit, transitions, splitScreenOptional,
-                pipTouchHandlerOptional, recentsTransitionHandler, keyguardTransitionHandler);
+                pipTouchHandlerOptional, recentsTransitionHandler, keyguardTransitionHandler,
+                desktopModeController, desktopTasksController);
     }
 
     @WMSingleton
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeController.java
index 1169af9..db6c258 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeController.java
@@ -33,6 +33,7 @@
 import android.app.ActivityManager.RunningTaskInfo;
 import android.app.WindowConfiguration;
 import android.content.Context;
+import android.content.res.TypedArray;
 import android.database.ContentObserver;
 import android.graphics.Region;
 import android.net.Uri;
@@ -414,6 +415,25 @@
     }
 
     /**
+     * Applies the proper surface states (rounded corners) to tasks when desktop mode is active.
+     * This is intended to be used when desktop mode is part of another animation but isn't, itself,
+     * animating.
+     */
+    public void syncSurfaceState(@NonNull TransitionInfo info,
+            SurfaceControl.Transaction finishTransaction) {
+        // Add rounded corners to freeform windows
+        final TypedArray ta = mContext.obtainStyledAttributes(
+                new int[]{android.R.attr.dialogCornerRadius});
+        final int cornerRadius = ta.getDimensionPixelSize(0, 0);
+        ta.recycle();
+        for (TransitionInfo.Change change: info.getChanges()) {
+            if (change.getTaskInfo().getWindowingMode() == WINDOWING_MODE_FREEFORM) {
+                finishTransaction.setCornerRadius(change.getLeash(), cornerRadius);
+            }
+        }
+    }
+
+    /**
      * A {@link ContentObserver} for listening to changes to {@link Settings.System#DESKTOP_MODE}
      */
     private final class SettingsObserver extends ContentObserver {
@@ -500,6 +520,11 @@
         }
 
         @Override
+        public void showDesktopApp(int taskId) throws RemoteException {
+            // TODO
+        }
+
+        @Override
         public int getVisibleTaskCount(int displayId) throws RemoteException {
             int[] result = new int[1];
             executeRemoteCallWithTaskPermission(mController, "getVisibleTaskCount",
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
index b551e6b..d191fcd 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
@@ -16,6 +16,7 @@
 
 package com.android.wm.shell.desktopmode
 
+import android.R
 import android.app.ActivityManager.RunningTaskInfo
 import android.app.WindowConfiguration.ACTIVITY_TYPE_HOME
 import android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD
@@ -24,6 +25,7 @@
 import android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED
 import android.app.WindowConfiguration.WindowingMode
 import android.content.Context
+import android.content.res.TypedArray
 import android.graphics.Point
 import android.graphics.Rect
 import android.graphics.Region
@@ -277,6 +279,11 @@
     }
 
     /** Move a task to the front */
+    fun moveTaskToFront(taskId: Int) {
+        shellTaskOrganizer.getRunningTaskInfo(taskId)?.let { task -> moveTaskToFront(task) }
+    }
+
+    /** Move a task to the front */
     fun moveTaskToFront(taskInfo: RunningTaskInfo) {
         KtProtoLog.v(
             WM_SHELL_DESKTOP_MODE,
@@ -460,6 +467,25 @@
         }
     }
 
+    /**
+     * Applies the proper surface states (rounded corners) to tasks when desktop mode is active.
+     * This is intended to be used when desktop mode is part of another animation but isn't, itself,
+     * animating.
+     */
+    fun syncSurfaceState(
+            info: TransitionInfo,
+            finishTransaction: SurfaceControl.Transaction
+    ) {
+        // Add rounded corners to freeform windows
+        val ta: TypedArray = context.obtainStyledAttributes(
+                intArrayOf(R.attr.dialogCornerRadius))
+        val cornerRadius = ta.getDimensionPixelSize(0, 0).toFloat()
+        ta.recycle()
+        info.changes
+                .filter { it.taskInfo.windowingMode == WINDOWING_MODE_FREEFORM }
+                .forEach { finishTransaction.setCornerRadius(it.leash, cornerRadius) }
+    }
+
     private fun handleFreeformTaskLaunch(task: RunningTaskInfo): WindowContainerTransaction? {
         val activeTasks = desktopModeTaskRepository.getActiveTasks(task.displayId)
         if (activeTasks.none { desktopModeTaskRepository.isVisibleTask(it) }) {
@@ -774,6 +800,13 @@
             ) { c -> c.hideStashedDesktopApps(displayId) }
         }
 
+        override fun showDesktopApp(taskId: Int) {
+            ExecutorUtils.executeRemoteCallWithTaskPermission(
+                    controller,
+                    "showDesktopApp"
+            ) { c -> c.moveTaskToFront(taskId) }
+        }
+
         override fun getVisibleTaskCount(displayId: Int): Int {
             val result = IntArray(1)
             ExecutorUtils.executeRemoteCallWithTaskPermission(
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/IDesktopMode.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/IDesktopMode.aidl
index 05a6e33..ee3a080 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/IDesktopMode.aidl
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/IDesktopMode.aidl
@@ -32,6 +32,9 @@
     /** Hide apps that may be stashed */
     void hideStashedDesktopApps(int displayId);
 
+    /** Bring task with the given id to front */
+    oneway void showDesktopApp(int taskId);
+
     /** Get count of visible desktop tasks on the given display */
     int getVisibleTaskCount(int displayId);
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/keyguard/KeyguardTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/keyguard/KeyguardTransitionHandler.java
index 658359e..c25352b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/keyguard/KeyguardTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/keyguard/KeyguardTransitionHandler.java
@@ -166,7 +166,7 @@
                     "unocclude",
                     transition, info, startTransaction, finishTransaction, finishCallback);
         } else {
-            Log.wtf(TAG, "Failed to play: " + info);
+            Log.w(TAG, "Failed to play: " + info);
             return false;
         }
     }
@@ -186,6 +186,7 @@
                         public void onTransitionFinished(
                                 WindowContainerTransaction wct, SurfaceControl.Transaction sct) {
                             mMainExecutor.execute(() -> {
+                                mStartedTransitions.remove(transition);
                                 finishCallback.onTransitionFinished(wct, null);
                             });
                         }
@@ -206,7 +207,7 @@
         final IRemoteTransition playing = mStartedTransitions.get(currentTransition);
 
         if (playing == null) {
-            ProtoLog.e(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, 
+            ProtoLog.e(ShellProtoLogGroup.WM_SHELL_TRANSITIONS,
                     "unknown keyguard transition %s", currentTransition);
             return;
         }
@@ -217,14 +218,17 @@
             // the device sleeping/waking, so it's best to ignore this and keep playing anyway.
             return;
         } else {
-            finishAnimationImmediately(currentTransition);
+            finishAnimationImmediately(currentTransition, playing);
         }
     }
 
     @Override
     public void onTransitionConsumed(IBinder transition, boolean aborted,
             SurfaceControl.Transaction finishTransaction) {
-        finishAnimationImmediately(transition);
+        final IRemoteTransition playing = mStartedTransitions.remove(transition);
+        if (playing != null) {
+            finishAnimationImmediately(transition, playing);
+        }
     }
 
     @Nullable
@@ -234,21 +238,17 @@
         return null;
     }
 
-    private void finishAnimationImmediately(IBinder transition) {
-        final IRemoteTransition playing = mStartedTransitions.get(transition);
-
-        if (playing != null) {
-            final IBinder fakeTransition = new Binder();
-            final TransitionInfo fakeInfo = new TransitionInfo(TRANSIT_SLEEP, 0x0);
-            final SurfaceControl.Transaction fakeT = new SurfaceControl.Transaction();
-            final FakeFinishCallback fakeFinishCb = new FakeFinishCallback();
-            try {
-                playing.mergeAnimation(fakeTransition, fakeInfo, fakeT, transition, fakeFinishCb);
-            } catch (RemoteException e) {
-                // There is no good reason for this to happen because the player is a local object
-                // implementing an AIDL interface.
-                Log.wtf(TAG, "RemoteException thrown from KeyguardService transition", e);
-            }
+    private void finishAnimationImmediately(IBinder transition, IRemoteTransition playing) {
+        final IBinder fakeTransition = new Binder();
+        final TransitionInfo fakeInfo = new TransitionInfo(TRANSIT_SLEEP, 0x0);
+        final SurfaceControl.Transaction fakeT = new SurfaceControl.Transaction();
+        final FakeFinishCallback fakeFinishCb = new FakeFinishCallback();
+        try {
+            playing.mergeAnimation(fakeTransition, fakeInfo, fakeT, transition, fakeFinishCb);
+        } catch (RemoteException e) {
+            // There is no good reason for this to happen because the player is a local object
+            // implementing an AIDL interface.
+            Log.wtf(TAG, "RemoteException thrown from KeyguardService transition", e);
         }
     }
 
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 d2b0e28..9863099 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
@@ -348,8 +348,6 @@
             WindowContainerTransaction wct,
             @Nullable RemoteTransition remoteTransition,
             Transitions.TransitionHandler handler,
-            @Nullable TransitionConsumedCallback consumedCallback,
-            @Nullable TransitionFinishedCallback finishedCallback,
             int extraTransitType, boolean resizeAnim) {
         if (mPendingEnter != null) {
             ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "  splitTransition "
@@ -357,20 +355,16 @@
             return null;
         }
         final IBinder transition = mTransitions.startTransition(transitType, wct, handler);
-        setEnterTransition(transition, remoteTransition, consumedCallback, finishedCallback,
-                extraTransitType, resizeAnim);
+        setEnterTransition(transition, remoteTransition, extraTransitType, resizeAnim);
         return transition;
     }
 
     /** Sets a transition to enter split. */
     void setEnterTransition(@NonNull IBinder transition,
             @Nullable RemoteTransition remoteTransition,
-            @Nullable TransitionConsumedCallback consumedCallback,
-            @Nullable TransitionFinishedCallback finishedCallback,
             int extraTransitType, boolean resizeAnim) {
         mPendingEnter = new EnterSession(
-                transition, consumedCallback, finishedCallback, remoteTransition, extraTransitType,
-                resizeAnim);
+                transition, remoteTransition, extraTransitType, resizeAnim);
 
         ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "  splitTransition "
                 + " deduced Enter split screen");
@@ -608,12 +602,10 @@
         final boolean mResizeAnim;
 
         EnterSession(IBinder transition,
-                @Nullable TransitionConsumedCallback consumedCallback,
-                @Nullable TransitionFinishedCallback finishedCallback,
                 @Nullable RemoteTransition remoteTransition,
                 int extraTransitType, boolean resizeAnim) {
-            super(transition, consumedCallback, finishedCallback, remoteTransition,
-                    extraTransitType);
+            super(transition, null /* consumedCallback */, null /* finishedCallback */,
+                    remoteTransition, extraTransitType);
             this.mResizeAnim = resizeAnim;
         }
     }
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 256d48c..b2526ee 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
@@ -396,7 +396,7 @@
         prepareEnterSplitScreen(wct, task, stagePosition);
         if (ENABLE_SHELL_TRANSITIONS) {
             mSplitTransitions.startEnterTransition(TRANSIT_TO_FRONT, wct,
-                    null, this, null /* consumedCallback */, null /* finishedCallback */,
+                    null, this,
                     isSplitScreenVisible()
                             ? TRANSIT_SPLIT_SCREEN_OPEN_TO_SIDE : TRANSIT_SPLIT_SCREEN_PAIR_OPEN,
                     !mIsDropEntering);
@@ -507,8 +507,7 @@
         prepareEnterSplitScreen(wct, null /* taskInfo */, position);
 
         mSplitTransitions.startEnterTransition(TRANSIT_TO_FRONT, wct, null, this,
-                null /* consumedCallback */, null /* finishedCallback */, extraTransitType,
-                !mIsDropEntering);
+                extraTransitType, !mIsDropEntering);
     }
 
     /** Launches an activity into split by legacy transition. */
@@ -665,8 +664,7 @@
         if (mPausingTasks.contains(mainTaskId)) {
             mPausingTasks.clear();
         }
-        mSplitTransitions.startEnterTransition(
-                TRANSIT_TO_FRONT, wct, remoteTransition, this, null, null,
+        mSplitTransitions.startEnterTransition(TRANSIT_TO_FRONT, wct, remoteTransition, this,
                 TRANSIT_SPLIT_SCREEN_PAIR_OPEN, false);
         setEnterInstanceId(instanceId);
     }
@@ -716,8 +714,7 @@
             wct.sendPendingIntent(pendingIntent2, fillInIntent2, options2);
         }
 
-        mSplitTransitions.startEnterTransition(
-                TRANSIT_TO_FRONT, wct, remoteTransition, this, null, null,
+        mSplitTransitions.startEnterTransition(TRANSIT_TO_FRONT, wct, remoteTransition, this,
                 TRANSIT_SPLIT_SCREEN_PAIR_OPEN, false);
         setEnterInstanceId(instanceId);
     }
@@ -1453,8 +1450,6 @@
         if (!mMainStage.isActive()) return;
         mSideStage.removeAllTasks(wct, stageToTop == STAGE_TYPE_SIDE);
         mMainStage.deactivate(wct, stageToTop == STAGE_TYPE_MAIN);
-        wct.setReparentLeafTaskIfRelaunch(mRootTaskInfo.token,
-                false /* reparentLeafTaskIfRelaunch */);
     }
 
     private void prepareEnterSplitScreen(WindowContainerTransaction wct) {
@@ -2295,14 +2290,28 @@
             out = new WindowContainerTransaction();
             final StageTaskListener stage = getStageOfTask(triggerTask);
             if (stage != null) {
-                // Dismiss split if the last task in one of the stages is going away
                 if (isClosingType(type) && stage.getChildCount() == 1) {
+                    // Dismiss split if the last task in one of the stages is going away
                     // The top should be the opposite side that is closing:
-                    int dismissTop = getStageType(stage) == STAGE_TYPE_MAIN ? STAGE_TYPE_SIDE
-                            : STAGE_TYPE_MAIN;
+                    int dismissTop = getStageType(stage) == STAGE_TYPE_MAIN
+                            ? STAGE_TYPE_SIDE : STAGE_TYPE_MAIN;
                     prepareExitSplitScreen(dismissTop, out);
                     mSplitTransitions.setDismissTransition(transition, dismissTop,
                             EXIT_REASON_APP_FINISHED);
+                } else if (isOpening && !mPausingTasks.isEmpty()) {
+                    // One of the splitting task is opening while animating the split pair in
+                    // recents, which means to dismiss the split pair to this task.
+                    int dismissTop = getStageType(stage) == STAGE_TYPE_MAIN
+                            ? STAGE_TYPE_MAIN : STAGE_TYPE_SIDE;
+                    prepareExitSplitScreen(dismissTop, out);
+                    mSplitTransitions.setDismissTransition(transition, dismissTop,
+                            EXIT_REASON_APP_FINISHED);
+                } else if (!isSplitScreenVisible() && isOpening) {
+                    // If split running backgroud and trigger task is appearing into split,
+                    // prepare to enter split screen.
+                    prepareEnterSplitScreen(out);
+                    mSplitTransitions.setEnterTransition(transition, request.getRemoteTransition(),
+                            TRANSIT_SPLIT_SCREEN_PAIR_OPEN, !mIsDropEntering);
                 }
             } else if (isOpening && inFullscreen) {
                 final int activityType = triggerTask.getActivityType();
@@ -2327,8 +2336,7 @@
                 out = new WindowContainerTransaction();
                 prepareEnterSplitScreen(out);
                 mSplitTransitions.setEnterTransition(transition, request.getRemoteTransition(),
-                        null /* consumedCallback */, null /* finishedCallback */,
-                        TRANSIT_SPLIT_SCREEN_OPEN_TO_SIDE, !mIsDropEntering);
+                        TRANSIT_SPLIT_SCREEN_PAIR_OPEN, !mIsDropEntering);
             }
         }
         return out;
@@ -2586,6 +2594,7 @@
         // First, verify that we actually have opened apps in both splits.
         TransitionInfo.Change mainChild = null;
         TransitionInfo.Change sideChild = null;
+        final WindowContainerTransaction evictWct = new WindowContainerTransaction();
         for (int iC = 0; iC < info.getChanges().size(); ++iC) {
             final TransitionInfo.Change change = info.getChanges().get(iC);
             final ActivityManager.RunningTaskInfo taskInfo = change.getTaskInfo();
@@ -2594,12 +2603,16 @@
                 continue;
             }
             final @StageType int stageType = getStageType(getStageOfTask(taskInfo));
-            if (stageType == STAGE_TYPE_MAIN
+            if (mainChild == null && stageType == STAGE_TYPE_MAIN
                     && (isOpeningType(change.getMode()) || change.getMode() == TRANSIT_CHANGE)) {
                 // Includes TRANSIT_CHANGE to cover reparenting top-most task to split.
                 mainChild = change;
-            } else if (stageType == STAGE_TYPE_SIDE && isOpeningType(change.getMode())) {
+            } else if (sideChild == null && stageType == STAGE_TYPE_SIDE
+                    && isOpeningType(change.getMode())) {
                 sideChild = change;
+            } else if (stageType != STAGE_TYPE_UNDEFINED && change.getMode() == TRANSIT_TO_BACK) {
+                // Collect all to back task's and evict them when transition finished.
+                evictWct.reparent(taskInfo.token, null /* parent */, false /* onTop */);
             }
         }
 
@@ -2661,10 +2674,14 @@
                     mSideStage.evictInvisibleChildren(callbackWct);
                 }
             }
+            if (!evictWct.isEmpty()) {
+                callbackWct.merge(evictWct, true);
+            }
             if (enterTransition.mResizeAnim) {
                 mShowDecorImmediately = true;
                 mSplitLayout.flingDividerToCenter();
             }
+            callbackWct.setReparentLeafTaskIfRelaunch(mRootTaskInfo.token, false);
             mPausingTasks.clear();
         });
 
@@ -2815,6 +2832,7 @@
         dismissTransition.setFinishedCallback((callbackWct, callbackT) -> {
             mMainStage.getSplitDecorManager().release(callbackT);
             mSideStage.getSplitDecorManager().release(callbackT);
+            callbackWct.setReparentLeafTaskIfRelaunch(mRootTaskInfo.token, false);
         });
 
         addDividerBarToTransition(info, false /* show */);
@@ -2868,8 +2886,7 @@
         }
 
         setSplitsVisible(false);
-        finishWct.setReparentLeafTaskIfRelaunch(mRootTaskInfo.token,
-                true /* reparentLeafTaskIfRelaunch */);
+        prepareExitSplitScreen(STAGE_TYPE_UNDEFINED, finishWct);
         logExit(EXIT_REASON_UNKNOWN);
     }
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java
index 64571e0..5ee5324 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java
@@ -40,6 +40,9 @@
 import android.window.WindowContainerTransactionCallback;
 
 import com.android.internal.protolog.common.ProtoLog;
+import com.android.wm.shell.desktopmode.DesktopModeController;
+import com.android.wm.shell.desktopmode.DesktopModeStatus;
+import com.android.wm.shell.desktopmode.DesktopTasksController;
 import com.android.wm.shell.keyguard.KeyguardTransitionHandler;
 import com.android.wm.shell.pip.PipTransitionController;
 import com.android.wm.shell.pip.phone.PipTouchHandler;
@@ -65,6 +68,8 @@
     private RecentsTransitionHandler mRecentsHandler;
     private StageCoordinator mSplitHandler;
     private final KeyguardTransitionHandler mKeyguardHandler;
+    private DesktopModeController mDesktopModeController;
+    private DesktopTasksController mDesktopTasksController;
 
     private static class MixedTransition {
         static final int TYPE_ENTER_PIP_FROM_SPLIT = 1;
@@ -81,6 +86,9 @@
         /** Keyguard exit/occlude/unocclude transition. */
         static final int TYPE_KEYGUARD = 5;
 
+        /** Recents Transition while in desktop mode. */
+        static final int TYPE_RECENTS_DURING_DESKTOP = 6;
+
         /** The default animation for this mixed transition. */
         static final int ANIM_TYPE_DEFAULT = 0;
 
@@ -132,7 +140,9 @@
             Optional<SplitScreenController> splitScreenControllerOptional,
             Optional<PipTouchHandler> pipTouchHandlerOptional,
             Optional<RecentsTransitionHandler> recentsHandlerOptional,
-            KeyguardTransitionHandler keyguardHandler) {
+            KeyguardTransitionHandler keyguardHandler,
+            Optional<DesktopModeController> desktopModeControllerOptional,
+            Optional<DesktopTasksController> desktopTasksControllerOptional) {
         mPlayer = player;
         mKeyguardHandler = keyguardHandler;
         if (Transitions.ENABLE_SHELL_TRANSITIONS && pipTouchHandlerOptional.isPresent()
@@ -149,6 +159,8 @@
                 if (mRecentsHandler != null) {
                     mRecentsHandler.addMixer(this);
                 }
+                mDesktopModeController = desktopModeControllerOptional.orElse(null);
+                mDesktopTasksController = desktopTasksControllerOptional.orElse(null);
             }, this);
         }
     }
@@ -218,7 +230,8 @@
 
     @Override
     public Transitions.TransitionHandler handleRecentsRequest(WindowContainerTransaction outWCT) {
-        if (mRecentsHandler != null && mSplitHandler.isSplitScreenVisible()) {
+        if (mRecentsHandler != null && (mSplitHandler.isSplitScreenVisible()
+                || DesktopModeStatus.isActive(mPlayer.getContext()))) {
             return this;
         }
         return null;
@@ -233,6 +246,13 @@
                     MixedTransition.TYPE_RECENTS_DURING_SPLIT, transition);
             mixed.mLeftoversHandler = mRecentsHandler;
             mActiveTransitions.add(mixed);
+        } else if (DesktopModeStatus.isActive(mPlayer.getContext())) {
+            ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " Got a recents request while "
+                    + "desktop mode is active, so treat it as Mixed.");
+            final MixedTransition mixed = new MixedTransition(
+                    MixedTransition.TYPE_RECENTS_DURING_DESKTOP, transition);
+            mixed.mLeftoversHandler = mRecentsHandler;
+            mActiveTransitions.add(mixed);
         } else {
             throw new IllegalStateException("Accepted a recents transition but don't know how to"
                     + " handle it");
@@ -306,6 +326,9 @@
         } else if (mixed.mType == MixedTransition.TYPE_KEYGUARD) {
             return animateKeyguard(mixed, info, startTransaction, finishTransaction,
                     finishCallback);
+        } else if (mixed.mType == MixedTransition.TYPE_RECENTS_DURING_DESKTOP) {
+            return animateRecentsDuringDesktop(mixed, info, startTransaction, finishTransaction,
+                    finishCallback);
         } else {
             mActiveTransitions.remove(mixed);
             throw new IllegalStateException("Starting mixed animation without a known mixed type? "
@@ -582,6 +605,30 @@
         return true;
     }
 
+    private boolean animateRecentsDuringDesktop(@NonNull final MixedTransition mixed,
+            @NonNull TransitionInfo info,
+            @NonNull SurfaceControl.Transaction startTransaction,
+            @NonNull SurfaceControl.Transaction finishTransaction,
+            @NonNull Transitions.TransitionFinishCallback finishCallback) {
+        boolean consumed = mRecentsHandler.startAnimation(
+                mixed.mTransition, info, startTransaction, finishTransaction, finishCallback);
+        if (!consumed) {
+            return false;
+        }
+        //Sync desktop mode state (proto 1)
+        if (mDesktopModeController != null) {
+            mDesktopModeController.syncSurfaceState(info, finishTransaction);
+            return true;
+        }
+        //Sync desktop mode state (proto 2)
+        if (mDesktopTasksController != null) {
+            mDesktopTasksController.syncSurfaceState(info, finishTransaction);
+            return true;
+        }
+
+        return false;
+    }
+
     @Override
     public void mergeAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
             @NonNull SurfaceControl.Transaction t, @NonNull IBinder mergeTarget,
@@ -625,6 +672,9 @@
                         finishCallback);
             } else if (mixed.mType == MixedTransition.TYPE_KEYGUARD) {
                 mKeyguardHandler.mergeAnimation(transition, info, t, mergeTarget, finishCallback);
+            } else if (mixed.mType == MixedTransition.TYPE_RECENTS_DURING_DESKTOP) {
+                mixed.mLeftoversHandler.mergeAnimation(transition, info, t, mergeTarget,
+                        finishCallback);
             } else {
                 throw new IllegalStateException("Playing a mixed transition with unknown type? "
                         + mixed.mType);
@@ -650,6 +700,8 @@
             mixed.mLeftoversHandler.onTransitionConsumed(transition, aborted, finishT);
         } else if (mixed.mType == MixedTransition.TYPE_KEYGUARD) {
             mKeyguardHandler.onTransitionConsumed(transition, aborted, finishT);
+        } else if (mixed.mType == MixedTransition.TYPE_RECENTS_DURING_DESKTOP) {
+            mixed.mLeftoversHandler.onTransitionConsumed(transition, aborted, finishT);
         }
     }
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java
index dc8a258..4c678a2 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java
@@ -327,6 +327,7 @@
         final int wallpaperTransit = getWallpaperTransitType(info);
         boolean isDisplayRotationAnimationStarted = false;
         final boolean isDreamTransition = isDreamTransition(info);
+        final boolean isOnlyTranslucent = isOnlyTranslucent(info);
 
         for (int i = info.getChanges().size() - 1; i >= 0; --i) {
             final TransitionInfo.Change change = info.getChanges().get(i);
@@ -452,6 +453,17 @@
                             final int layer = zSplitLine + numChanges - i;
                             startTransaction.setLayer(change.getLeash(), layer);
                         }
+                    } else if (isOnlyTranslucent && TransitionUtil.isOpeningType(info.getType())
+                                && TransitionUtil.isClosingType(mode)) {
+                        // If there is a closing translucent task in an OPENING transition, we will
+                        // actually select a CLOSING animation, so move the closing task into
+                        // the animating part of the z-order.
+
+                        // See Transitions#setupAnimHierarchy for details about these variables.
+                        final int numChanges = info.getChanges().size();
+                        final int zSplitLine = numChanges + 1;
+                        final int layer = zSplitLine + numChanges - i;
+                        startTransaction.setLayer(change.getLeash(), layer);
                     }
                 }
 
@@ -543,6 +555,29 @@
         return false;
     }
 
+    /**
+     * Does `info` only contain translucent visibility changes (CHANGEs are ignored). We select
+     * different animations and z-orders for these
+     */
+    private static boolean isOnlyTranslucent(@NonNull TransitionInfo info) {
+        int translucentOpen = 0;
+        int translucentClose = 0;
+        for (int i = info.getChanges().size() - 1; i >= 0; --i) {
+            final TransitionInfo.Change change = info.getChanges().get(i);
+            if (change.getMode() == TRANSIT_CHANGE) continue;
+            if (change.hasFlags(FLAG_TRANSLUCENT)) {
+                if (TransitionUtil.isOpeningType(change.getMode())) {
+                    translucentOpen += 1;
+                } else {
+                    translucentClose += 1;
+                }
+            } else {
+                return false;
+            }
+        }
+        return (translucentOpen + translucentClose) > 0;
+    }
+
     @Override
     public void mergeAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
             @NonNull SurfaceControl.Transaction t, @NonNull IBinder mergeTarget,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/TransitionAnimationHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/TransitionAnimationHelper.java
index fc301b6..d978eaf 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/TransitionAnimationHelper.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/TransitionAnimationHelper.java
@@ -103,10 +103,11 @@
             // We will translucent open animation for translucent activities and tasks. Choose
             // WindowAnimation_activityOpenEnterAnimation and set translucent here, then
             // TransitionAnimation loads appropriate animation later.
-            if ((changeFlags & FLAG_TRANSLUCENT) != 0 && enter) {
-                translucent = true;
-            }
-            if (isTask && !translucent) {
+            translucent = (changeFlags & FLAG_TRANSLUCENT) != 0;
+            if (isTask && translucent && !enter) {
+                // For closing translucent tasks, use the activity close animation
+                animAttr = R.styleable.WindowAnimation_activityCloseExitAnimation;
+            } else if (isTask && !translucent) {
                 animAttr = enter
                         ? R.styleable.WindowAnimation_taskOpenEnterAnimation
                         : R.styleable.WindowAnimation_taskOpenExitAnimation;
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 3b306e7..ce8d792 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
@@ -16,9 +16,12 @@
 
 package com.android.wm.shell.transition;
 
+import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
 import static android.view.WindowManager.TRANSIT_CHANGE;
 import static android.view.WindowManager.TRANSIT_CLOSE;
 import static android.view.WindowManager.TRANSIT_FIRST_CUSTOM;
+import static android.view.WindowManager.TRANSIT_KEYGUARD_OCCLUDE;
 import static android.view.WindowManager.TRANSIT_OPEN;
 import static android.view.WindowManager.TRANSIT_SLEEP;
 import static android.view.WindowManager.TRANSIT_TO_BACK;
@@ -567,8 +570,8 @@
                     layer = zSplitLine + numChanges - i;
                 }
             } else { // CHANGE or other
-                if (isClosing) {
-                    // Put below CLOSE mode.
+                if (isClosing || TransitionUtil.isOrderOnly(change)) {
+                    // Put below CLOSE mode (in the "static" section).
                     layer = zSplitLine - i;
                 } else {
                     // Put above CLOSE mode.
@@ -1081,6 +1084,16 @@
                 }
             }
         }
+        if (request.getType() == TRANSIT_KEYGUARD_OCCLUDE && request.getTriggerTask() != null
+                && request.getTriggerTask().getWindowingMode() == WINDOWING_MODE_FREEFORM) {
+            // This freeform task is on top of keyguard, so its windowing mode should be changed to
+            // fullscreen.
+            if (wct == null) {
+                wct = new WindowContainerTransaction();
+            }
+            wct.setWindowingMode(request.getTriggerTask().token, WINDOWING_MODE_FULLSCREEN);
+            wct.setBounds(request.getTriggerTask().token, null);
+        }
         mOrganizer.startTransition(transitionToken, wct != null && wct.isEmpty() ? null : wct);
         active.mToken = transitionToken;
         // Currently, WMCore only does one transition at a time. If it makes a requestStart, it
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java
index ac5ff20..67ec6cf 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java
@@ -248,6 +248,9 @@
             mCaptionInsetsRect.bottom = mCaptionInsetsRect.top + captionHeight + params.mCaptionY;
             wct.addInsetsSource(mTaskInfo.token,
                     mOwner, 0 /* index */, WindowInsets.Type.captionBar(), mCaptionInsetsRect);
+            wct.addInsetsSource(mTaskInfo.token,
+                    mOwner, 0 /* index */, WindowInsets.Type.mandatorySystemGestures(),
+                    mCaptionInsetsRect);
         } else {
             startT.hide(mCaptionContainerSurface);
         }
@@ -345,6 +348,8 @@
         final WindowContainerTransaction wct = mWindowContainerTransactionSupplier.get();
         wct.removeInsetsSource(mTaskInfo.token,
                 mOwner, 0 /* index */, WindowInsets.Type.captionBar());
+        wct.removeInsetsSource(mTaskInfo.token,
+                mOwner, 0 /* index */, WindowInsets.Type.mandatorySystemGestures());
         mTaskOrganizer.applyTransaction(wct);
     }
 
diff --git a/libs/WindowManager/Shell/tests/flicker/Android.bp b/libs/WindowManager/Shell/tests/flicker/Android.bp
index 78a2a38..e382a0f 100644
--- a/libs/WindowManager/Shell/tests/flicker/Android.bp
+++ b/libs/WindowManager/Shell/tests/flicker/Android.bp
@@ -70,6 +70,7 @@
     ],
     data: [
         ":FlickerTestApp",
+        "trace_config/*",
     ],
 }
 
diff --git a/libs/WindowManager/Shell/tests/flicker/AndroidTestTemplate.xml b/libs/WindowManager/Shell/tests/flicker/AndroidTestTemplate.xml
index 8818aa2..ffe6d6a2 100644
--- a/libs/WindowManager/Shell/tests/flicker/AndroidTestTemplate.xml
+++ b/libs/WindowManager/Shell/tests/flicker/AndroidTestTemplate.xml
@@ -16,6 +16,9 @@
   -->
 <configuration description="Runs WindowManager Shell Flicker Tests {MODULE}">
     <option name="test-tag" value="FlickerTests" />
+    <!-- Needed for storing the perfetto trace files in the sdcard/test_results-->
+    <option name="isolated-storage" value="false" />
+
     <target_preparer class="com.android.tradefed.targetprep.DeviceSetup">
         <!-- keeps the screen on during tests -->
         <option name="screen-always-on" value="on" />
@@ -41,22 +44,53 @@
         <option name="run-command" value="settings put secure show_ime_with_hard_keyboard 1" />
         <option name="run-command" value="settings put system show_touches 1" />
         <option name="run-command" value="settings put system pointer_location 1" />
-        <option name="teardown-command" value="settings delete secure show_ime_with_hard_keyboard" />
+        <option name="teardown-command"
+                value="settings delete secure show_ime_with_hard_keyboard" />
         <option name="teardown-command" value="settings delete system show_touches" />
         <option name="teardown-command" value="settings delete system pointer_location" />
-        <option name="teardown-command" value="cmd overlay enable com.android.internal.systemui.navbar.gestural" />
+        <option name="teardown-command"
+                value="cmd overlay enable com.android.internal.systemui.navbar.gestural" />
     </target_preparer>
     <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
         <option name="cleanup-apks" value="true"/>
         <option name="test-file-name" value="{MODULE}.apk"/>
         <option name="test-file-name" value="FlickerTestApp.apk" />
     </target_preparer>
+    <!-- Needed for pushing the trace config file -->
+    <target_preparer class="com.android.tradefed.targetprep.RootTargetPreparer"/>
+    <target_preparer class="com.android.tradefed.targetprep.PushFilePreparer">
+        <option name="push-file"
+                key="trace_config.textproto"
+                value="/data/misc/perfetto-traces/trace_config.textproto"
+        />
+        <!--Install the content provider automatically when we push some file in sdcard folder.-->
+        <!--Needed to avoid the installation during the test suite.-->
+        <option name="push-file" key="trace_config.textproto" value="/sdcard/sample.textproto" />
+    </target_preparer>
     <test class="com.android.tradefed.testtype.AndroidJUnitTest">
         <option name="package" value="{PACKAGE}"/>
         <option name="shell-timeout" value="6600s" />
         <option name="test-timeout" value="6000s" />
         <option name="hidden-api-checks" value="false" />
+        <option name="device-listeners" value="android.device.collectors.PerfettoListener" />
+        <!-- PerfettoListener related arguments -->
+        <option name="instrumentation-arg" key="perfetto_config_text_proto" value="true" />
+        <option name="instrumentation-arg"
+                key="perfetto_config_file"
+                value="trace_config.textproto"
+        />
+        <option name="instrumentation-arg" key="per_run" value="true" />
     </test>
+    <!-- Needed for pulling the collected trace config on to the host -->
+    <metrics_collector class="com.android.tradefed.device.metric.FilePullerLogCollector">
+        <option name="pull-pattern-keys" value="perfetto_file_path" />
+    </metrics_collector>
+    <metrics_collector class="com.android.tradefed.device.metric.FilePullerLogCollector">
+        <option name="pull-pattern-keys" value="(\w)+\.winscope" />
+        <option name="pull-pattern-keys" value="(\w)+\.mp4" />
+        <option name="collect-on-run-ended-only" value="false" />
+        <option name="clean-up" value="true" />
+    </metrics_collector>
     <metrics_collector class="com.android.tradefed.device.metric.FilePullerLogCollector">
         <option name="directory-keys" value="/sdcard/flicker" />
         <option name="collect-on-run-ended-only" value="true" />
diff --git a/libs/WindowManager/Shell/tests/flicker/trace_config/trace_config.textproto b/libs/WindowManager/Shell/tests/flicker/trace_config/trace_config.textproto
new file mode 100644
index 0000000..406ada9
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/trace_config/trace_config.textproto
@@ -0,0 +1,75 @@
+# Copyright (C) 2023 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# proto-message: TraceConfig
+
+# Enable periodic flushing of the trace buffer into the output file.
+write_into_file: true
+
+# Writes the userspace buffer into the file every 1s.
+file_write_period_ms: 2500
+
+# See b/126487238 - we need to guarantee ordering of events.
+flush_period_ms: 30000
+
+# The trace buffers needs to be big enough to hold |file_write_period_ms| of
+# trace data. The trace buffer sizing depends on the number of trace categories
+# enabled and the device activity.
+
+# RSS events
+buffers: {
+  size_kb: 63488
+  fill_policy: RING_BUFFER
+}
+
+data_sources {
+  config {
+    name: "linux.process_stats"
+    target_buffer: 0
+    # polled per-process memory counters and process/thread names.
+    # If you don't want the polled counters, remove the "process_stats_config"
+    # section, but keep the data source itself as it still provides on-demand
+    # thread/process naming for ftrace data below.
+    process_stats_config {
+      scan_all_processes_on_start: true
+    }
+  }
+}
+
+data_sources: {
+  config {
+    name: "linux.ftrace"
+    ftrace_config {
+      ftrace_events: "ftrace/print"
+      ftrace_events: "task/task_newtask"
+      ftrace_events: "task/task_rename"
+      atrace_categories: "ss"
+      atrace_categories: "wm"
+      atrace_categories: "am"
+      atrace_categories: "aidl"
+      atrace_categories: "input"
+      atrace_categories: "binder_driver"
+      atrace_categories: "sched_process_exit"
+      atrace_apps: "com.android.server.wm.flicker.testapp"
+      atrace_apps: "com.android.systemui"
+      atrace_apps: "com.android.wm.shell.flicker"
+      atrace_apps: "com.android.wm.shell.flicker.other"
+      atrace_apps: "com.android.wm.shell.flicker.bubbles"
+      atrace_apps: "com.android.wm.shell.flicker.pip"
+      atrace_apps: "com.android.wm.shell.flicker.splitscreen"
+      atrace_apps: "com.google.android.apps.nexuslauncher"
+    }
+  }
+}
+
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
index 9ce18db..e3dd449 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
@@ -345,6 +345,18 @@
     }
 
     @Test
+    fun moveTaskToFront_postsWctWithReorderOp() {
+        val task1 = setUpFreeformTask()
+        val task2 = setUpFreeformTask()
+
+        controller.moveTaskToFront(task1)
+
+        val wct = getLatestWct(expectTransition = TRANSIT_TO_FRONT)
+        assertThat(wct.hierarchyOps).hasSize(1)
+        wct.assertReorderAt(index = 0, task1)
+    }
+
+    @Test
     fun moveToNextDisplay_noOtherDisplays() {
         whenever(rootTaskDisplayAreaOrganizer.displayIds).thenReturn(intArrayOf(DEFAULT_DISPLAY))
         val task = setUpFreeformTask(displayId = DEFAULT_DISPLAY)
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 5f705d7..3b05651 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
@@ -185,7 +185,7 @@
 
         IBinder transition = mSplitScreenTransitions.startEnterTransition(
                 TRANSIT_OPEN, new WindowContainerTransaction(),
-                new RemoteTransition(testRemote, "Test"), mStageCoordinator, null, null,
+                new RemoteTransition(testRemote, "Test"), mStageCoordinator,
                 TRANSIT_SPLIT_SCREEN_PAIR_OPEN, false);
         mMainStage.onTaskAppeared(mMainChild, createMockSurface());
         mSideStage.onTaskAppeared(mSideChild, createMockSurface());
@@ -412,7 +412,7 @@
         IBinder enterTransit = mSplitScreenTransitions.startEnterTransition(
                 TRANSIT_OPEN, new WindowContainerTransaction(),
                 new RemoteTransition(new TestRemoteTransition(), "Test"),
-                mStageCoordinator, null, null, TRANSIT_SPLIT_SCREEN_PAIR_OPEN, false);
+                mStageCoordinator, TRANSIT_SPLIT_SCREEN_PAIR_OPEN, false);
         mMainStage.onTaskAppeared(mMainChild, createMockSurface());
         mSideStage.onTaskAppeared(mSideChild, createMockSurface());
         mStageCoordinator.startAnimation(enterTransit, enterInfo,
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java
index 5a2326b..f941e95 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java
@@ -316,7 +316,8 @@
         releaseOrder.verify(t).remove(captionContainerSurface);
         releaseOrder.verify(t).remove(decorContainerSurface);
         releaseOrder.verify(t).apply();
-        verify(mMockWindowContainerTransaction)
+        // Expect to remove two insets sources, the caption insets and the mandatory gesture insets.
+        verify(mMockWindowContainerTransaction, Mockito.times(2))
                 .removeInsetsSource(eq(taskInfo.token), any(), anyInt(), anyInt());
     }
 
diff --git a/libs/hwui/Properties.cpp b/libs/hwui/Properties.cpp
index 7af6efb..06aed63 100644
--- a/libs/hwui/Properties.cpp
+++ b/libs/hwui/Properties.cpp
@@ -17,7 +17,6 @@
 #include "Properties.h"
 
 #include "Debug.h"
-#include "log/log_main.h"
 #ifdef __ANDROID__
 #include "HWUIProperties.sysprop.h"
 #endif
@@ -220,12 +219,15 @@
     return sRenderPipelineType;
 }
 
-void Properties::overrideRenderPipelineType(RenderPipelineType type, bool inUnitTest) {
+void Properties::overrideRenderPipelineType(RenderPipelineType type) {
     // If we're doing actual rendering then we can't change the renderer after it's been set.
-    // Unit tests can freely change this as often as it wants.
-    LOG_ALWAYS_FATAL_IF(sRenderPipelineType != RenderPipelineType::NotInitialized &&
-                                sRenderPipelineType != type && !inUnitTest,
-                        "Trying to change pipeline but it's already set.");
+    // Unit tests can freely change this as often as it wants, though, as there's no actual
+    // GL rendering happening
+    if (sRenderPipelineType != RenderPipelineType::NotInitialized) {
+        LOG_ALWAYS_FATAL_IF(sRenderPipelineType != type,
+                            "Trying to change pipeline but it's already set");
+        return;
+    }
     sRenderPipelineType = type;
 }
 
diff --git a/libs/hwui/Properties.h b/libs/hwui/Properties.h
index 24e206b..bb47744 100644
--- a/libs/hwui/Properties.h
+++ b/libs/hwui/Properties.h
@@ -303,7 +303,7 @@
     static bool enableRTAnimations;
 
     // Used for testing only to change the render pipeline.
-    static void overrideRenderPipelineType(RenderPipelineType, bool inUnitTest = false);
+    static void overrideRenderPipelineType(RenderPipelineType);
 
     static bool runningInEmulator;
 
diff --git a/libs/hwui/renderthread/VulkanManager.cpp b/libs/hwui/renderthread/VulkanManager.cpp
index 4cffc6c..d4e919f 100644
--- a/libs/hwui/renderthread/VulkanManager.cpp
+++ b/libs/hwui/renderthread/VulkanManager.cpp
@@ -23,13 +23,11 @@
 #include <GrDirectContext.h>
 #include <GrTypes.h>
 #include <android/sync.h>
+#include <gui/TraceUtils.h>
 #include <ui/FatVector.h>
 #include <vk/GrVkExtensions.h>
 #include <vk/GrVkTypes.h>
 
-#include <cstring>
-
-#include <gui/TraceUtils.h>
 #include "Properties.h"
 #include "RenderThread.h"
 #include "pipeline/skia/ShaderCache.h"
@@ -90,19 +88,6 @@
     }
 }
 
-GrVkGetProc VulkanManager::sSkiaGetProp = [](const char* proc_name, VkInstance instance,
-                                             VkDevice device) {
-    if (device != VK_NULL_HANDLE) {
-        if (strcmp("vkQueueSubmit", proc_name) == 0) {
-            return (PFN_vkVoidFunction)VulkanManager::interceptedVkQueueSubmit;
-        } else if (strcmp("vkQueueWaitIdle", proc_name) == 0) {
-            return (PFN_vkVoidFunction)VulkanManager::interceptedVkQueueWaitIdle;
-        }
-        return vkGetDeviceProcAddr(device, proc_name);
-    }
-    return vkGetInstanceProcAddr(instance, proc_name);
-};
-
 #define GET_PROC(F) m##F = (PFN_vk##F)vkGetInstanceProcAddr(VK_NULL_HANDLE, "vk" #F)
 #define GET_INST_PROC(F) m##F = (PFN_vk##F)vkGetInstanceProcAddr(mInstance, "vk" #F)
 #define GET_DEV_PROC(F) m##F = (PFN_vk##F)vkGetDeviceProcAddr(mDevice, "vk" #F)
@@ -138,6 +123,7 @@
     }
 
     mGraphicsQueue = VK_NULL_HANDLE;
+    mAHBUploadQueue = VK_NULL_HANDLE;
     mDevice = VK_NULL_HANDLE;
     mPhysicalDevice = VK_NULL_HANDLE;
     mInstance = VK_NULL_HANDLE;
@@ -231,7 +217,7 @@
     mDriverVersion = physDeviceProperties.driverVersion;
 
     // query to get the initial queue props size
-    uint32_t queueCount;
+    uint32_t queueCount = 0;
     mGetPhysicalDeviceQueueFamilyProperties(mPhysicalDevice, &queueCount, nullptr);
     LOG_ALWAYS_FATAL_IF(!queueCount);
 
@@ -239,11 +225,14 @@
     std::unique_ptr<VkQueueFamilyProperties[]> queueProps(new VkQueueFamilyProperties[queueCount]);
     mGetPhysicalDeviceQueueFamilyProperties(mPhysicalDevice, &queueCount, queueProps.get());
 
+    constexpr auto kRequestedQueueCount = 2;
+
     // iterate to find the graphics queue
     mGraphicsQueueIndex = queueCount;
     for (uint32_t i = 0; i < queueCount; i++) {
         if (queueProps[i].queueFlags & VK_QUEUE_GRAPHICS_BIT) {
             mGraphicsQueueIndex = i;
+            LOG_ALWAYS_FATAL_IF(queueProps[i].queueCount < kRequestedQueueCount);
             break;
         }
     }
@@ -273,7 +262,14 @@
         LOG_ALWAYS_FATAL_IF(!hasKHRSwapchainExtension);
     }
 
-    grExtensions.init(sSkiaGetProp, mInstance, mPhysicalDevice, mInstanceExtensions.size(),
+    auto getProc = [](const char* proc_name, VkInstance instance, VkDevice device) {
+        if (device != VK_NULL_HANDLE) {
+            return vkGetDeviceProcAddr(device, proc_name);
+        }
+        return vkGetInstanceProcAddr(instance, proc_name);
+    };
+
+    grExtensions.init(getProc, mInstance, mPhysicalDevice, mInstanceExtensions.size(),
                       mInstanceExtensions.data(), mDeviceExtensions.size(),
                       mDeviceExtensions.data());
 
@@ -312,7 +308,7 @@
     // and we can't depend on it on all platforms
     features.features.robustBufferAccess = VK_FALSE;
 
-    float queuePriorities[1] = {0.0};
+    float queuePriorities[kRequestedQueueCount] = {0.0};
 
     void* queueNextPtr = nullptr;
 
@@ -345,7 +341,7 @@
             queueNextPtr,                                // pNext
             0,                                           // VkDeviceQueueCreateFlags
             mGraphicsQueueIndex,                         // queueFamilyIndex
-            1,                                           // queueCount
+            kRequestedQueueCount,                        // queueCount
             queuePriorities,                             // pQueuePriorities
     };
 
@@ -403,6 +399,7 @@
     this->setupDevice(mExtensions, mPhysicalDeviceFeatures2);
 
     mGetDeviceQueue(mDevice, mGraphicsQueueIndex, 0, &mGraphicsQueue);
+    mGetDeviceQueue(mDevice, mGraphicsQueueIndex, 1, &mAHBUploadQueue);
 
     if (Properties::enablePartialUpdates && Properties::useBufferAge) {
         mSwapBehavior = SwapBehavior::BufferAge;
@@ -416,16 +413,24 @@
 
 sk_sp<GrDirectContext> VulkanManager::createContext(GrContextOptions& options,
                                                     ContextType contextType) {
+    auto getProc = [](const char* proc_name, VkInstance instance, VkDevice device) {
+        if (device != VK_NULL_HANDLE) {
+            return vkGetDeviceProcAddr(device, proc_name);
+        }
+        return vkGetInstanceProcAddr(instance, proc_name);
+    };
+
     GrVkBackendContext backendContext;
     backendContext.fInstance = mInstance;
     backendContext.fPhysicalDevice = mPhysicalDevice;
     backendContext.fDevice = mDevice;
-    backendContext.fQueue = mGraphicsQueue;
+    backendContext.fQueue =
+            (contextType == ContextType::kRenderThread) ? mGraphicsQueue : mAHBUploadQueue;
     backendContext.fGraphicsQueueIndex = mGraphicsQueueIndex;
     backendContext.fMaxAPIVersion = mAPIVersion;
     backendContext.fVkExtensions = &mExtensions;
     backendContext.fDeviceFeatures2 = &mPhysicalDeviceFeatures2;
-    backendContext.fGetProc = sSkiaGetProp;
+    backendContext.fGetProc = std::move(getProc);
 
     LOG_ALWAYS_FATAL_IF(options.fContextDeleteProc != nullptr, "Conflicting fContextDeleteProcs!");
     this->incStrong((void*)onGrContextReleased);
@@ -636,8 +641,6 @@
         ALOGE_IF(VK_SUCCESS != err, "VulkanManager::swapBuffers(): Failed to get semaphore Fd");
     } else {
         ALOGE("VulkanManager::swapBuffers(): Semaphore submission failed");
-
-        std::lock_guard<std::mutex> lock(mGraphicsQueueMutex);
         mQueueWaitIdle(mGraphicsQueue);
     }
     if (mDestroySemaphoreContext) {
@@ -652,7 +655,6 @@
 void VulkanManager::destroySurface(VulkanSurface* surface) {
     // Make sure all submit commands have finished before starting to destroy objects.
     if (VK_NULL_HANDLE != mGraphicsQueue) {
-        std::lock_guard<std::mutex> lock(mGraphicsQueueMutex);
         mQueueWaitIdle(mGraphicsQueue);
     }
     mDeviceWaitIdle(mDevice);
diff --git a/libs/hwui/renderthread/VulkanManager.h b/libs/hwui/renderthread/VulkanManager.h
index 00a40c0..2be1ffd 100644
--- a/libs/hwui/renderthread/VulkanManager.h
+++ b/libs/hwui/renderthread/VulkanManager.h
@@ -17,10 +17,6 @@
 #ifndef VULKANMANAGER_H
 #define VULKANMANAGER_H
 
-#include <functional>
-#include <mutex>
-
-#include "vulkan/vulkan_core.h"
 #if !defined(VK_USE_PLATFORM_ANDROID_KHR)
 #define VK_USE_PLATFORM_ANDROID_KHR
 #endif
@@ -186,25 +182,8 @@
     VkDevice mDevice = VK_NULL_HANDLE;
 
     uint32_t mGraphicsQueueIndex;
-
-    std::mutex mGraphicsQueueMutex;
     VkQueue mGraphicsQueue = VK_NULL_HANDLE;
-
-    static VKAPI_ATTR VkResult interceptedVkQueueSubmit(VkQueue queue, uint32_t submitCount,
-                                                        const VkSubmitInfo* pSubmits,
-                                                        VkFence fence) {
-        sp<VulkanManager> manager = VulkanManager::getInstance();
-        std::lock_guard<std::mutex> lock(manager->mGraphicsQueueMutex);
-        return manager->mQueueSubmit(queue, submitCount, pSubmits, fence);
-    }
-
-    static VKAPI_ATTR VkResult interceptedVkQueueWaitIdle(VkQueue queue) {
-        sp<VulkanManager> manager = VulkanManager::getInstance();
-        std::lock_guard<std::mutex> lock(manager->mGraphicsQueueMutex);
-        return manager->mQueueWaitIdle(queue);
-    }
-
-    static GrVkGetProc sSkiaGetProp;
+    VkQueue mAHBUploadQueue = VK_NULL_HANDLE;
 
     // Variables saved to populate VkFunctorInitParams.
     static const uint32_t mAPIVersion = VK_MAKE_VERSION(1, 1, 0);
diff --git a/libs/hwui/tests/common/TestUtils.h b/libs/hwui/tests/common/TestUtils.h
index 9d5c13e..81ecfe5 100644
--- a/libs/hwui/tests/common/TestUtils.h
+++ b/libs/hwui/tests/common/TestUtils.h
@@ -61,12 +61,12 @@
         ADD_FAILURE() << "ClipState not a rect";                                     \
     }
 
-#define INNER_PIPELINE_TEST(test_case_name, test_name, pipeline, functionCall)      \
-    TEST(test_case_name, test_name##_##pipeline) {                                  \
-        RenderPipelineType oldType = Properties::getRenderPipelineType();           \
-        Properties::overrideRenderPipelineType(RenderPipelineType::pipeline, true); \
-        functionCall;                                                               \
-        Properties::overrideRenderPipelineType(oldType, true);                      \
+#define INNER_PIPELINE_TEST(test_case_name, test_name, pipeline, functionCall) \
+    TEST(test_case_name, test_name##_##pipeline) {                             \
+        RenderPipelineType oldType = Properties::getRenderPipelineType();      \
+        Properties::overrideRenderPipelineType(RenderPipelineType::pipeline);  \
+        functionCall;                                                          \
+        Properties::overrideRenderPipelineType(oldType);                       \
     };
 
 #define INNER_PIPELINE_RENDERTHREAD_TEST(test_case_name, test_name, pipeline) \
@@ -78,27 +78,29 @@
  * Like gtest's TEST, but runs on the RenderThread, and 'renderThread' is passed, in top level scope
  * (for e.g. accessing its RenderState)
  */
-#define RENDERTHREAD_TEST(test_case_name, test_name)                         \
-    class test_case_name##_##test_name##_RenderThreadTest {                  \
-    public:                                                                  \
-        static void doTheThing(renderthread::RenderThread& renderThread);    \
-    };                                                                       \
-    INNER_PIPELINE_RENDERTHREAD_TEST(test_case_name, test_name, SkiaGL);     \
-    INNER_PIPELINE_RENDERTHREAD_TEST(test_case_name, test_name, SkiaVulkan); \
-    void test_case_name##_##test_name##_RenderThreadTest::doTheThing(        \
+#define RENDERTHREAD_TEST(test_case_name, test_name)                                        \
+    class test_case_name##_##test_name##_RenderThreadTest {                                 \
+    public:                                                                                 \
+        static void doTheThing(renderthread::RenderThread& renderThread);                   \
+    };                                                                                      \
+    INNER_PIPELINE_RENDERTHREAD_TEST(test_case_name, test_name, SkiaGL);                    \
+    /* Temporarily disabling Vulkan until we can figure out a way to stub out the driver */ \
+    /* INNER_PIPELINE_RENDERTHREAD_TEST(test_case_name, test_name, SkiaVulkan); */          \
+    void test_case_name##_##test_name##_RenderThreadTest::doTheThing(                       \
             renderthread::RenderThread& renderThread)
 
 /**
  * Like RENDERTHREAD_TEST, but only runs with the Skia RenderPipelineTypes
  */
-#define RENDERTHREAD_SKIA_PIPELINE_TEST(test_case_name, test_name)           \
-    class test_case_name##_##test_name##_RenderThreadTest {                  \
-    public:                                                                  \
-        static void doTheThing(renderthread::RenderThread& renderThread);    \
-    };                                                                       \
-    INNER_PIPELINE_RENDERTHREAD_TEST(test_case_name, test_name, SkiaGL);     \
-    INNER_PIPELINE_RENDERTHREAD_TEST(test_case_name, test_name, SkiaVulkan); \
-    void test_case_name##_##test_name##_RenderThreadTest::doTheThing(        \
+#define RENDERTHREAD_SKIA_PIPELINE_TEST(test_case_name, test_name)                          \
+    class test_case_name##_##test_name##_RenderThreadTest {                                 \
+    public:                                                                                 \
+        static void doTheThing(renderthread::RenderThread& renderThread);                   \
+    };                                                                                      \
+    INNER_PIPELINE_RENDERTHREAD_TEST(test_case_name, test_name, SkiaGL);                    \
+    /* Temporarily disabling Vulkan until we can figure out a way to stub out the driver */ \
+    /* INNER_PIPELINE_RENDERTHREAD_TEST(test_case_name, test_name, SkiaVulkan); */          \
+    void test_case_name##_##test_name##_RenderThreadTest::doTheThing(                       \
             renderthread::RenderThread& renderThread)
 
 /**
diff --git a/media/java/android/media/MediaRoute2ProviderService.java b/media/java/android/media/MediaRoute2ProviderService.java
index 11cb2be..1e3c154 100644
--- a/media/java/android/media/MediaRoute2ProviderService.java
+++ b/media/java/android/media/MediaRoute2ProviderService.java
@@ -81,6 +81,18 @@
     public static final String SERVICE_INTERFACE = "android.media.MediaRoute2ProviderService";
 
     /**
+     * A category indicating that the associated provider is only intended for use within the app
+     * that hosts the provider.
+     *
+     * <p>Declaring this category helps the system save resources by avoiding the launch of services
+     * whose routes are known to be private to the app that provides them.
+     *
+     * @hide
+     */
+    public static final String CATEGORY_SELF_SCAN_ONLY =
+            "android.media.MediaRoute2ProviderService.SELF_SCAN_ONLY";
+
+    /**
      * The request ID to pass {@link #notifySessionCreated(long, RoutingSessionInfo)}
      * when {@link MediaRoute2ProviderService} created a session although there was no creation
      * request.
diff --git a/media/java/android/media/RouteListingPreference.java b/media/java/android/media/RouteListingPreference.java
index ee1f203..3935de8 100644
--- a/media/java/android/media/RouteListingPreference.java
+++ b/media/java/android/media/RouteListingPreference.java
@@ -440,14 +440,14 @@
          * <p>If this method returns {@link #SUBTEXT_CUSTOM}, then the subtext is obtained form
          * {@link #getCustomSubtextMessage()}.
          *
-         * @see #SUBTEXT_NONE,
-         * @see #SUBTEXT_ERROR_UNKNOWN,
-         * @see #SUBTEXT_SUBSCRIPTION_REQUIRED,
-         * @see #SUBTEXT_DOWNLOADED_CONTENT_ROUTING_DISALLOWED,
-         * @see #SUBTEXT_AD_ROUTING_DISALLOWED,
-         * @see #SUBTEXT_DEVICE_LOW_POWER,
-         * @see #SUBTEXT_UNAUTHORIZED ,
-         * @see #SUBTEXT_TRACK_UNSUPPORTED,
+         * @see #SUBTEXT_NONE
+         * @see #SUBTEXT_ERROR_UNKNOWN
+         * @see #SUBTEXT_SUBSCRIPTION_REQUIRED
+         * @see #SUBTEXT_DOWNLOADED_CONTENT_ROUTING_DISALLOWED
+         * @see #SUBTEXT_AD_ROUTING_DISALLOWED
+         * @see #SUBTEXT_DEVICE_LOW_POWER
+         * @see #SUBTEXT_UNAUTHORIZED
+         * @see #SUBTEXT_TRACK_UNSUPPORTED
          * @see #SUBTEXT_CUSTOM
          */
         @SubText
diff --git a/media/java/android/media/tv/TvInputService.java b/media/java/android/media/tv/TvInputService.java
index 85b02ad..720d9a6 100644
--- a/media/java/android/media/tv/TvInputService.java
+++ b/media/java/android/media/tv/TvInputService.java
@@ -63,6 +63,7 @@
 import com.android.internal.os.SomeArgs;
 import com.android.internal.util.Preconditions;
 
+import java.io.IOException;
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.util.ArrayList;
@@ -1009,6 +1010,13 @@
          * @param buffer the {@link AdBuffer} that was consumed.
          */
         public void notifyAdBufferConsumed(@NonNull AdBuffer buffer) {
+            AdBuffer dupBuffer;
+            try {
+                dupBuffer = AdBuffer.dupAdBuffer(buffer);
+            } catch (IOException e) {
+                Log.w(TAG, "dup AdBuffer error in notifyAdBufferConsumed:", e);
+                return;
+            }
             executeOrPostRunnableOnMainThread(new Runnable() {
                 @MainThread
                 @Override
@@ -1016,10 +1024,14 @@
                     try {
                         if (DEBUG) Log.d(TAG, "notifyAdBufferConsumed");
                         if (mSessionCallback != null) {
-                            mSessionCallback.onAdBufferConsumed(buffer);
+                            mSessionCallback.onAdBufferConsumed(dupBuffer);
                         }
                     } catch (RemoteException e) {
                         Log.w(TAG, "error in notifyAdBufferConsumed", e);
+                    } finally {
+                        if (dupBuffer != null) {
+                            dupBuffer.getSharedMemory().close();
+                        }
                     }
                 }
             });
diff --git a/media/java/android/media/tv/interactive/TvInteractiveAppService.java b/media/java/android/media/tv/interactive/TvInteractiveAppService.java
index ec85cc7..2419404 100755
--- a/media/java/android/media/tv/interactive/TvInteractiveAppService.java
+++ b/media/java/android/media/tv/interactive/TvInteractiveAppService.java
@@ -1964,6 +1964,13 @@
          */
         @CallSuper
         public void notifyAdBufferReady(@NonNull AdBuffer buffer) {
+            AdBuffer dupBuffer;
+            try {
+                dupBuffer = AdBuffer.dupAdBuffer(buffer);
+            } catch (IOException e) {
+                Log.w(TAG, "dup AdBuffer error in notifyAdBufferReady:", e);
+                return;
+            }
             executeOrPostRunnableOnMainThread(new Runnable() {
                 @MainThread
                 @Override
@@ -1974,10 +1981,14 @@
                                     "notifyAdBufferReady(buffer=" + buffer + ")");
                         }
                         if (mSessionCallback != null) {
-                            mSessionCallback.onAdBufferReady(AdBuffer.dupAdBuffer(buffer));
+                            mSessionCallback.onAdBufferReady(dupBuffer);
                         }
-                    } catch (RemoteException | IOException e) {
+                    } catch (RemoteException e) {
                         Log.w(TAG, "error in notifyAdBuffer", e);
+                    } finally {
+                        if (dupBuffer != null) {
+                            dupBuffer.getSharedMemory().close();
+                        }
                     }
                 }
             });
diff --git a/packages/CarrierDefaultApp/assets/slice_purchase_test.html b/packages/CarrierDefaultApp/assets/slice_purchase_test.html
index d2c1c042..917276b 100644
--- a/packages/CarrierDefaultApp/assets/slice_purchase_test.html
+++ b/packages/CarrierDefaultApp/assets/slice_purchase_test.html
@@ -75,5 +75,11 @@
         Notify purchase failed
     </button>
     <p id="purchase_failed"></p>
+
+    <h2>Dismiss flow</h2>
+    <button type="button" onclick="testDismissFlow()">
+        Dismiss flow
+    </button>
+    <p id="dismiss_flow"></p>
 </body>
 </html>
diff --git a/packages/CarrierDefaultApp/assets/slice_purchase_test.js b/packages/CarrierDefaultApp/assets/slice_purchase_test.js
index 84ab1f9..be397a1 100644
--- a/packages/CarrierDefaultApp/assets/slice_purchase_test.js
+++ b/packages/CarrierDefaultApp/assets/slice_purchase_test.js
@@ -31,3 +31,9 @@
     document.getElementById("purchase_failed").innerHTML =
             "Notified purchase failed.";
 }
+
+function testDismissFlow() {
+    DataBoostWebServiceFlow.dismissFlow();
+    document.getElementById("dismiss_flow").innerHTML =
+            "Called dismiss flow.";
+}
diff --git a/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/DataBoostWebServiceFlow.java b/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/DataBoostWebServiceFlow.java
index 0aadd31..4500a22 100644
--- a/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/DataBoostWebServiceFlow.java
+++ b/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/DataBoostWebServiceFlow.java
@@ -88,4 +88,21 @@
             @Nullable String failureReason) {
         mActivity.onPurchaseFailed(failureCode, failureReason);
     }
+
+    /**
+     * Interface method allowing the carrier website to notify the slice purchase application that
+     * the service flow ended prematurely. This can be due to user action, an error in the
+     * web sheet logic, or an error on the network side.
+     *
+     * This can be called using the JavaScript below:
+     * <script type="text/javascript">
+     *     function dismissFlow() {
+     *         DataBoostWebServiceFlow.dismissFlow();
+     *     }
+     * </script>
+     */
+    @JavascriptInterface
+    public void dismissFlow() {
+        mActivity.onDismissFlow();
+    }
 }
diff --git a/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/SlicePurchaseActivity.java b/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/SlicePurchaseActivity.java
index d304394..2530257d6 100644
--- a/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/SlicePurchaseActivity.java
+++ b/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/SlicePurchaseActivity.java
@@ -27,6 +27,7 @@
 import android.text.TextUtils;
 import android.util.Log;
 import android.view.KeyEvent;
+import android.webkit.CookieManager;
 import android.webkit.WebView;
 
 import com.android.phone.slice.SlicePurchaseController;
@@ -137,6 +138,14 @@
         finishAndRemoveTask();
     }
 
+    protected void onDismissFlow() {
+        logd("onDismissFlow: Dismiss flow called while purchasing premium capability "
+                + TelephonyManager.convertPremiumCapabilityToString(mCapability));
+        SlicePurchaseBroadcastReceiver.sendSlicePurchaseAppResponse(
+                mIntent, SlicePurchaseController.EXTRA_INTENT_REQUEST_FAILED);
+        finishAndRemoveTask();
+    }
+
     @Override
     public boolean onKeyDown(int keyCode, @NonNull KeyEvent event) {
         // Pressing back in the WebView will go to the previous page instead of closing
@@ -168,6 +177,12 @@
         // Create WebView
         mWebView = new WebView(this);
 
+        // Clear any cookies and state that might be saved from previous sessions
+        CookieManager.getInstance().removeAllCookies(null);
+        CookieManager.getInstance().flush();
+        mWebView.clearCache(true);
+        mWebView.clearHistory();
+
         // Enable JavaScript for the carrier purchase website to send results back to
         //  the slice purchase application.
         mWebView.getSettings().setJavaScriptEnabled(true);
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 e7a75e5..cc103fa 100644
--- a/packages/CarrierDefaultApp/tests/unit/src/com/android/carrierdefaultapp/SlicePurchaseActivityTest.java
+++ b/packages/CarrierDefaultApp/tests/unit/src/com/android/carrierdefaultapp/SlicePurchaseActivityTest.java
@@ -55,6 +55,7 @@
     @Mock PendingIntent mPendingIntent;
     @Mock PendingIntent mSuccessfulIntent;
     @Mock PendingIntent mCanceledIntent;
+    @Mock PendingIntent mRequestFailedIntent;
     @Mock CarrierConfigManager mCarrierConfigManager;
     @Mock NotificationManager mNotificationManager;
     @Mock PersistableBundle mPersistableBundle;
@@ -112,6 +113,11 @@
         doReturn(true).when(mSuccessfulIntent).isBroadcast();
         doReturn(mSuccessfulIntent).when(spiedIntent).getParcelableExtra(
                 eq(SlicePurchaseController.EXTRA_INTENT_SUCCESS), eq(PendingIntent.class));
+        doReturn(TelephonyManager.PHONE_PROCESS_NAME).when(mRequestFailedIntent)
+                .getCreatorPackage();
+        doReturn(true).when(mRequestFailedIntent).isBroadcast();
+        doReturn(mRequestFailedIntent).when(spiedIntent).getParcelableExtra(
+                eq(SlicePurchaseController.EXTRA_INTENT_REQUEST_FAILED), eq(PendingIntent.class));
 
         mSlicePurchaseActivity = startActivity(spiedIntent, null, null);
     }
@@ -124,7 +130,7 @@
 
     @Test
     public void testOnPurchaseFailed() throws Exception {
-        int failureCode = SlicePurchaseController.FAILURE_CODE_SERVER_UNREACHABLE;
+        int failureCode = SlicePurchaseController.FAILURE_CODE_CARRIER_URL_UNAVAILABLE;
         String failureReason = "Server unreachable";
         mSlicePurchaseActivity.onPurchaseFailed(failureCode, failureReason);
         ArgumentCaptor<Intent> intentCaptor = ArgumentCaptor.forClass(Intent.class);
@@ -141,4 +147,10 @@
         mSlicePurchaseActivity.onDestroy();
         verify(mCanceledIntent).send();
     }
+
+    @Test
+    public void testOnDismissFlow() throws Exception {
+        mSlicePurchaseActivity.onDismissFlow();
+        verify(mRequestFailedIntent).send();
+    }
 }
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsTypography.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsTypography.kt
index 5eaa495..460bf99 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsTypography.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsTypography.kt
@@ -21,6 +21,7 @@
 import androidx.compose.runtime.remember
 import androidx.compose.ui.text.TextStyle
 import androidx.compose.ui.text.font.FontWeight
+import androidx.compose.ui.text.style.Hyphens
 import androidx.compose.ui.unit.em
 import androidx.compose.ui.unit.sp
 
@@ -34,42 +35,48 @@
             fontWeight = FontWeight.Normal,
             fontSize = 57.sp,
             lineHeight = 64.sp,
-            letterSpacing = (-0.2).sp
+            letterSpacing = (-0.2).sp,
+            hyphens = Hyphens.Auto,
         ),
         displayMedium = TextStyle(
             fontFamily = brand,
             fontWeight = FontWeight.Normal,
             fontSize = 45.sp,
             lineHeight = 52.sp,
-            letterSpacing = 0.0.sp
+            letterSpacing = 0.0.sp,
+            hyphens = Hyphens.Auto,
         ),
         displaySmall = TextStyle(
             fontFamily = brand,
             fontWeight = FontWeight.Normal,
             fontSize = 36.sp,
             lineHeight = 44.sp,
-            letterSpacing = 0.0.sp
+            letterSpacing = 0.0.sp,
+            hyphens = Hyphens.Auto,
         ),
         headlineLarge = TextStyle(
             fontFamily = brand,
             fontWeight = FontWeight.Normal,
             fontSize = 32.sp,
             lineHeight = 40.sp,
-            letterSpacing = 0.0.sp
+            letterSpacing = 0.0.sp,
+            hyphens = Hyphens.Auto,
         ),
         headlineMedium = TextStyle(
             fontFamily = brand,
             fontWeight = FontWeight.Normal,
             fontSize = 28.sp,
             lineHeight = 36.sp,
-            letterSpacing = 0.0.sp
+            letterSpacing = 0.0.sp,
+            hyphens = Hyphens.Auto,
         ),
         headlineSmall = TextStyle(
             fontFamily = brand,
             fontWeight = FontWeight.Normal,
             fontSize = 24.sp,
             lineHeight = 32.sp,
-            letterSpacing = 0.0.sp
+            letterSpacing = 0.0.sp,
+            hyphens = Hyphens.Auto,
         ),
         titleLarge = TextStyle(
             fontFamily = brand,
@@ -77,6 +84,7 @@
             fontSize = 22.sp,
             lineHeight = 28.sp,
             letterSpacing = 0.02.em,
+            hyphens = Hyphens.Auto,
         ),
         titleMedium = TextStyle(
             fontFamily = brand,
@@ -84,6 +92,7 @@
             fontSize = 20.sp,
             lineHeight = 24.sp,
             letterSpacing = 0.02.em,
+            hyphens = Hyphens.Auto,
         ),
         titleSmall = TextStyle(
             fontFamily = brand,
@@ -91,6 +100,7 @@
             fontSize = 18.sp,
             lineHeight = 20.sp,
             letterSpacing = 0.02.em,
+            hyphens = Hyphens.Auto,
         ),
         bodyLarge = TextStyle(
             fontFamily = plain,
@@ -98,6 +108,7 @@
             fontSize = 16.sp,
             lineHeight = 24.sp,
             letterSpacing = 0.01.em,
+            hyphens = Hyphens.Auto,
         ),
         bodyMedium = TextStyle(
             fontFamily = plain,
@@ -105,6 +116,7 @@
             fontSize = 14.sp,
             lineHeight = 20.sp,
             letterSpacing = 0.01.em,
+            hyphens = Hyphens.Auto,
         ),
         bodySmall = TextStyle(
             fontFamily = plain,
@@ -112,6 +124,7 @@
             fontSize = 12.sp,
             lineHeight = 16.sp,
             letterSpacing = 0.01.em,
+            hyphens = Hyphens.Auto,
         ),
         labelLarge = TextStyle(
             fontFamily = plain,
@@ -119,6 +132,7 @@
             fontSize = 16.sp,
             lineHeight = 24.sp,
             letterSpacing = 0.01.em,
+            hyphens = Hyphens.Auto,
         ),
         labelMedium = TextStyle(
             fontFamily = plain,
@@ -126,6 +140,7 @@
             fontSize = 14.sp,
             lineHeight = 20.sp,
             letterSpacing = 0.01.em,
+            hyphens = Hyphens.Auto,
         ),
         labelSmall = TextStyle(
             fontFamily = plain,
@@ -133,6 +148,7 @@
             fontSize = 12.sp,
             lineHeight = 16.sp,
             letterSpacing = 0.01.em,
+            hyphens = Hyphens.Auto,
         ),
     )
 }
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/Text.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/Text.kt
index 5f2344e..01ba8f8 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/Text.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/Text.kt
@@ -19,6 +19,7 @@
 import androidx.compose.foundation.layout.Box
 import androidx.compose.foundation.layout.Column
 import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.padding
 import androidx.compose.foundation.layout.width
 import androidx.compose.material3.MaterialTheme
 import androidx.compose.material3.Text
@@ -29,6 +30,7 @@
 import androidx.compose.ui.text.style.TextOverflow
 import androidx.compose.ui.tooling.preview.Preview
 import androidx.compose.ui.unit.dp
+import com.android.settingslib.spa.framework.theme.SettingsDimension
 import com.android.settingslib.spa.framework.theme.SettingsTheme
 import com.android.settingslib.spa.framework.theme.toMediumWeight
 
@@ -78,7 +80,7 @@
 @Composable
 fun PlaceholderTitle(title: String) {
     Box(
-        modifier = Modifier.fillMaxSize(),
+        modifier = Modifier.fillMaxSize().padding(SettingsDimension.itemPadding),
         contentAlignment = Alignment.Center,
     ) {
         Text(
diff --git a/packages/SettingsLib/TEST_MAPPING b/packages/SettingsLib/TEST_MAPPING
new file mode 100644
index 0000000..f6ada4c1a
--- /dev/null
+++ b/packages/SettingsLib/TEST_MAPPING
@@ -0,0 +1,13 @@
+{
+  "presubmit": [
+    {
+      "name": "SettingsLibUnitTests"
+    },
+    {
+      "name": "SpaPrivilegedLibTests"
+    },
+    {
+      "name": "SettingsSpaUnitTests"
+    }
+  ]
+}
diff --git a/packages/SettingsLib/res/values/strings.xml b/packages/SettingsLib/res/values/strings.xml
index f581091..dac7f8d 100644
--- a/packages/SettingsLib/res/values/strings.xml
+++ b/packages/SettingsLib/res/values/strings.xml
@@ -1327,6 +1327,12 @@
     <string name="media_transfer_this_device_name" product="default">This phone</string>
     <!-- Name of the tablet device. [CHAR LIMIT=30] -->
     <string name="media_transfer_this_device_name" product="tablet">This tablet</string>
+    <!-- Name of the dock device. [CHAR LIMIT=30] -->
+    <string name="media_transfer_dock_speaker_device_name">Dock speaker</string>
+    <!-- Default name of the external device. [CHAR LIMIT=30] -->
+    <string name="media_transfer_external_device_name">External Device</string>
+    <!-- Default name of the connected device. [CHAR LIMIT=30] -->
+    <string name="media_transfer_default_device_name">Connected device</string>
     <!-- Name of the phone device with an active remote session. [CHAR LIMIT=30] -->
     <string name="media_transfer_this_phone">This phone</string>
     <!-- Sub status indicates device is not available due to an unknown error. [CHAR LIMIT=NONE] -->
diff --git a/packages/SettingsLib/res/values/styles.xml b/packages/SettingsLib/res/values/styles.xml
index 2584be7..4ac4aae 100644
--- a/packages/SettingsLib/res/values/styles.xml
+++ b/packages/SettingsLib/res/values/styles.xml
@@ -17,6 +17,7 @@
 <resources xmlns:androidprv="http://schemas.android.com/apk/prv/res/android">
     <style name="TextAppearanceSmall">
         <item name="android:textAppearance">?android:attr/textAppearanceSmall</item>
+        <item name="android:textColor">?android:attr/textColorSecondary</item>
     </style>
     <style name="TextAppearanceMedium">
         <item name="android:textAppearance">?android:attr/textAppearanceMedium</item>
@@ -91,7 +92,7 @@
         <item name="android:ellipsize">end</item>
     </style>
 
-    <style name="DialogButtonPositive"  xmlns:androidprv="http://schemas.android.com/apk/prv/res/android">
+    <style name="DialogButtonPositive">
         <item name="android:buttonCornerRadius">0dp</item>
         <item name="android:background">@drawable/dialog_btn_filled</item>
         <item name="android:textColor">?androidprv:attr/textColorOnAccent</item>
@@ -105,7 +106,6 @@
     <style name="DialogButtonNegative">
         <item name="android:buttonCornerRadius">28dp</item>
         <item name="android:background">@drawable/dialog_btn_outline</item>
-        <item name="android:buttonCornerRadius">28dp</item>
         <item name="android:textColor">?android:attr/textColorPrimary</item>
         <item name="android:textSize">14sp</item>
         <item name="android:lineHeight">20sp</item>
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothBroadcastUtils.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothBroadcastUtils.java
index a80061e..2bca7cf 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothBroadcastUtils.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothBroadcastUtils.java
@@ -43,39 +43,5 @@
     /**
      * Bluetooth scheme.
      */
-    public static final String SCHEME_BT_BROADCAST_METADATA = "BT:";
-
-    // BluetoothLeBroadcastMetadata
-    static final String PREFIX_BT_ADDRESS_TYPE = "T:";
-    static final String PREFIX_BT_DEVICE = "D:";
-    static final String PREFIX_BT_ADVERTISING_SID = "AS:";
-    static final String PREFIX_BT_BROADCAST_ID = "B:";
-    static final String PREFIX_BT_SYNC_INTERVAL = "SI:";
-    static final String PREFIX_BT_IS_ENCRYPTED = "E:";
-    static final String PREFIX_BT_BROADCAST_CODE = "C:";
-    static final String PREFIX_BT_PRESENTATION_DELAY = "PD:";
-    static final String PREFIX_BT_SUBGROUPS = "SG:";
-    static final String PREFIX_BT_ANDROID_VERSION = "V:";
-
-    // BluetoothLeBroadcastSubgroup
-    static final String PREFIX_BTSG_CODEC_ID = "CID:";
-    static final String PREFIX_BTSG_CODEC_CONFIG = "CC:";
-    static final String PREFIX_BTSG_AUDIO_CONTENT = "AC:";
-    static final String PREFIX_BTSG_CHANNEL_PREF = "CP:";
-    static final String PREFIX_BTSG_BROADCAST_CHANNEL = "BC:";
-
-    // BluetoothLeAudioCodecConfigMetadata
-    static final String PREFIX_BTCC_AUDIO_LOCATION = "AL:";
-    static final String PREFIX_BTCC_RAW_METADATA = "CCRM:";
-
-    // BluetoothLeAudioContentMetadata
-    static final String PREFIX_BTAC_PROGRAM_INFO = "PI:";
-    static final String PREFIX_BTAC_LANGUAGE = "L:";
-    static final String PREFIX_BTAC_RAW_METADATA = "ACRM:";
-
-    // BluetoothLeBroadcastChannel
-    static final String PREFIX_BTBC_CHANNEL_INDEX = "CI:";
-    static final String PREFIX_BTBC_CODEC_CONFIG = "BCCM:";
-
-    static final String DELIMITER_QR_CODE = ";";
+    public static final String SCHEME_BT_BROADCAST_METADATA = "BT:BluetoothLeBroadcastMetadata:";
 }
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothLeBroadcastMetadataExt.kt b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothLeBroadcastMetadataExt.kt
new file mode 100644
index 0000000..b54b115
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothLeBroadcastMetadataExt.kt
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.settingslib.bluetooth
+
+import android.bluetooth.BluetoothLeBroadcastMetadata
+import android.os.Parcel
+import android.os.Parcelable
+import android.util.Base64
+import android.util.Log
+import com.android.settingslib.bluetooth.BluetoothBroadcastUtils.SCHEME_BT_BROADCAST_METADATA
+
+object BluetoothLeBroadcastMetadataExt {
+    private const val TAG = "BluetoothLeBroadcastMetadataExt"
+
+    /**
+     * Converts [BluetoothLeBroadcastMetadata] to QR code string.
+     *
+     * QR code string will prefix with "BT:BluetoothLeBroadcastMetadata:".
+     */
+    fun BluetoothLeBroadcastMetadata.toQrCodeString(): String =
+        SCHEME_BT_BROADCAST_METADATA + Base64.encodeToString(toBytes(this), Base64.NO_WRAP)
+
+    /**
+     * Converts QR code string to [BluetoothLeBroadcastMetadata].
+     *
+     * QR code string should prefix with "BT:BluetoothLeBroadcastMetadata:".
+     */
+    fun convertToBroadcastMetadata(qrCodeString: String): BluetoothLeBroadcastMetadata? {
+        if (!qrCodeString.startsWith(SCHEME_BT_BROADCAST_METADATA)) return null
+        return try {
+            val encodedString = qrCodeString.removePrefix(SCHEME_BT_BROADCAST_METADATA)
+            val bytes = Base64.decode(encodedString, Base64.NO_WRAP)
+            createFromBytes(BluetoothLeBroadcastMetadata.CREATOR, bytes)
+        } catch (e: Exception) {
+            Log.w(TAG, "Cannot convert QR code string to BluetoothLeBroadcastMetadata", e)
+            null
+        }
+    }
+
+    private fun toBytes(parcelable: Parcelable): ByteArray =
+        Parcel.obtain().run {
+            parcelable.writeToParcel(this, 0)
+            setDataPosition(0)
+            val bytes = marshall()
+            recycle()
+            bytes
+        }
+
+    private fun <T> createFromBytes(creator: Parcelable.Creator<T>, bytes: ByteArray): T =
+        Parcel.obtain().run {
+            unmarshall(bytes, 0, bytes.size)
+            setDataPosition(0)
+            val created = creator.createFromParcel(this)
+            recycle()
+            created
+        }
+}
\ No newline at end of file
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcastMetadata.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcastMetadata.java
deleted file mode 100644
index 0630a2e..0000000
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcastMetadata.java
+++ /dev/null
@@ -1,454 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.settingslib.bluetooth;
-
-import android.bluetooth.BluetoothAdapter;
-import android.bluetooth.BluetoothDevice;
-import android.bluetooth.BluetoothLeAudioCodecConfigMetadata;
-import android.bluetooth.BluetoothLeAudioContentMetadata;
-import android.bluetooth.BluetoothLeBroadcastChannel;
-import android.bluetooth.BluetoothLeBroadcastMetadata;
-import android.bluetooth.BluetoothLeBroadcastSubgroup;
-import android.util.Log;
-
-import java.nio.charset.StandardCharsets;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-
-public class LocalBluetoothLeBroadcastMetadata {
-    private static final boolean DEBUG = BluetoothUtils.D;
-    private static final String TAG = "LocalBluetoothLeBroadcastMetadata";
-    private static final String METADATA_START = "<";
-    private static final String METADATA_END = ">";
-    private static final String PATTERN_REGEX = "<(.*?)>";
-    private static final String PATTERN_BT_BROADCAST_METADATA =
-            "T:<(.*?)>;+D:<(.*?)>;+AS:<(.*?)>;+B:<(.*?)>;+SI:<(.*?)>;+E:<(.*?)>;+C:<(.*?)>;"
-                + "+PD:<(.*?)>;+SG:(.*)";
-    private static final String PATTERN_BT_SUBGROUP =
-            "CID:<(.*?)>;+CC:<(.*?);>;+AC:<(.*?);>;+CP:<(.*?)>;+BC:<(.*)>;>;";
-    private static final String PATTERN_BT_CHANNEL = "CI:<(.*?)>;+BCCM:<(.*?);>;";
-
-    /* Index for BluetoothLeBroadcastMetadata */
-    private static int MATCH_INDEX_ADDRESS_TYPE = 1;
-    private static int MATCH_INDEX_DEVICE = 2;
-    private static int MATCH_INDEX_ADVERTISING_SID = 3;
-    private static int MATCH_INDEX_BROADCAST_ID = 4;
-    private static int MATCH_INDEX_SYNC_INTERVAL = 5;
-    private static int MATCH_INDEX_IS_ENCRYPTED = 6;
-    private static int MATCH_INDEX_BROADCAST_CODE = 7;
-    private static int MATCH_INDEX_PRESENTATION_DELAY = 8;
-    private static int MATCH_INDEX_SUBGROUPS = 9;
-
-    /* Index for BluetoothLeBroadcastSubgroup */
-    private static int MATCH_INDEX_CODEC_ID = 1;
-    private static int MATCH_INDEX_CODEC_CONFIG = 2;
-    private static int MATCH_INDEX_AUDIO_CONTENT = 3;
-    private static int MATCH_INDEX_CHANNEL_PREF = 4;
-    private static int MATCH_INDEX_BROADCAST_CHANNEL = 5;
-
-    /* Index for BluetoothLeAudioCodecConfigMetadata */
-    private static int LIST_INDEX_AUDIO_LOCATION = 0;
-    private static int LIST_INDEX_CODEC_CONFIG_RAW_METADATA = 1;
-
-    /* Index for BluetoothLeAudioContentMetadata */
-    private static int LIST_INDEX_PROGRAM_INFO = 0;
-    private static int LIST_INDEX_LANGUAGE = 1;
-    private static int LIST_INDEX_AUDIO_CONTENT_RAW_METADATA = 2;
-
-    /* Index for BluetoothLeBroadcastChannel */
-    private static int MATCH_INDEX_CHANNEL_INDEX = 1;
-    private static int MATCH_INDEX_CHANNEL_CODEC_CONFIG = 2;
-
-    private BluetoothLeBroadcastSubgroup mSubgroup;
-    private List<BluetoothLeBroadcastSubgroup> mSubgroupList;
-
-    // BluetoothLeBroadcastMetadata
-    // Optional: Identity address type
-    private int mSourceAddressType;
-    // Optional: Must use identity address
-    private BluetoothDevice mSourceDevice;
-    private int mSourceAdvertisingSid;
-    private int mBroadcastId;
-    private int mPaSyncInterval;
-    private int mPresentationDelayMicros;
-    private boolean mIsEncrypted;
-    private byte[] mBroadcastCode;
-
-    // BluetoothLeBroadcastSubgroup
-    private int mCodecId;
-    private BluetoothLeAudioContentMetadata mContentMetadata;
-    private BluetoothLeAudioCodecConfigMetadata mConfigMetadata;
-    private Boolean mNoChannelPreference;
-    private List<BluetoothLeBroadcastChannel> mChannel;
-
-    // BluetoothLeAudioCodecConfigMetadata
-    private long mAudioLocation;
-    private byte[] mCodecConfigMetadata;
-
-    // BluetoothLeAudioContentMetadata
-    private String mLanguage;
-    private String mProgramInfo;
-    private byte[] mAudioContentMetadata;
-
-    // BluetoothLeBroadcastChannel
-    private boolean mIsSelected;
-    private int mChannelIndex;
-
-
-    LocalBluetoothLeBroadcastMetadata(BluetoothLeBroadcastMetadata metadata) {
-        mSourceAddressType = metadata.getSourceAddressType();
-        mSourceDevice = metadata.getSourceDevice();
-        mSourceAdvertisingSid = metadata.getSourceAdvertisingSid();
-        mBroadcastId = metadata.getBroadcastId();
-        mPaSyncInterval = metadata.getPaSyncInterval();
-        mIsEncrypted = metadata.isEncrypted();
-        mBroadcastCode = metadata.getBroadcastCode();
-        mPresentationDelayMicros = metadata.getPresentationDelayMicros();
-        mSubgroupList = metadata.getSubgroups();
-    }
-
-    public LocalBluetoothLeBroadcastMetadata() {
-    }
-
-    public void setBroadcastCode(byte[] code) {
-        mBroadcastCode = code;
-    }
-
-    public int getBroadcastId() {
-        return mBroadcastId;
-    }
-
-    public String convertToQrCodeString() {
-        String subgroupString = convertSubgroupToString(mSubgroupList);
-        return new StringBuilder()
-                .append(BluetoothBroadcastUtils.SCHEME_BT_BROADCAST_METADATA)
-                .append(BluetoothBroadcastUtils.PREFIX_BT_ADDRESS_TYPE)
-                .append(METADATA_START).append(mSourceAddressType).append(METADATA_END)
-                .append(BluetoothBroadcastUtils.DELIMITER_QR_CODE)
-                .append(BluetoothBroadcastUtils.PREFIX_BT_DEVICE)
-                .append(METADATA_START).append(mSourceDevice).append(METADATA_END)
-                .append(BluetoothBroadcastUtils.DELIMITER_QR_CODE)
-                .append(BluetoothBroadcastUtils.PREFIX_BT_ADVERTISING_SID)
-                .append(METADATA_START).append(mSourceAdvertisingSid).append(METADATA_END)
-                .append(BluetoothBroadcastUtils.DELIMITER_QR_CODE)
-                .append(BluetoothBroadcastUtils.PREFIX_BT_BROADCAST_ID)
-                .append(METADATA_START).append(mBroadcastId).append(METADATA_END)
-                .append(BluetoothBroadcastUtils.DELIMITER_QR_CODE)
-                .append(BluetoothBroadcastUtils.PREFIX_BT_SYNC_INTERVAL)
-                .append(METADATA_START).append(mPaSyncInterval).append(METADATA_END)
-                .append(BluetoothBroadcastUtils.DELIMITER_QR_CODE)
-                .append(BluetoothBroadcastUtils.PREFIX_BT_IS_ENCRYPTED)
-                .append(METADATA_START).append(mIsEncrypted).append(METADATA_END)
-                .append(BluetoothBroadcastUtils.DELIMITER_QR_CODE)
-                .append(BluetoothBroadcastUtils.PREFIX_BT_BROADCAST_CODE)
-                .append(METADATA_START).append(Arrays.toString(mBroadcastCode)).append(METADATA_END)
-                .append(BluetoothBroadcastUtils.DELIMITER_QR_CODE)
-                .append(BluetoothBroadcastUtils.PREFIX_BT_PRESENTATION_DELAY)
-                .append(METADATA_START).append(mPresentationDelayMicros).append(METADATA_END)
-                .append(BluetoothBroadcastUtils.DELIMITER_QR_CODE)
-                .append(BluetoothBroadcastUtils.PREFIX_BT_SUBGROUPS)
-                .append(METADATA_START).append(subgroupString).append(METADATA_END)
-                .append(BluetoothBroadcastUtils.DELIMITER_QR_CODE)
-                .toString();
-    }
-
-    private String convertSubgroupToString(List<BluetoothLeBroadcastSubgroup> subgroupList) {
-        StringBuilder subgroupListBuilder = new StringBuilder();
-        String subgroupString = "";
-        for (BluetoothLeBroadcastSubgroup subgroup: subgroupList) {
-            String audioCodec = convertAudioCodecConfigToString(subgroup.getCodecSpecificConfig());
-            String audioContent = convertAudioContentToString(subgroup.getContentMetadata());
-            boolean hasChannelPreference = subgroup.hasChannelPreference();
-            String channels = convertChannelToString(subgroup.getChannels());
-            subgroupString = new StringBuilder()
-                    .append(BluetoothBroadcastUtils.PREFIX_BTSG_CODEC_ID)
-                    .append(METADATA_START).append(subgroup.getCodecId()).append(METADATA_END)
-                    .append(BluetoothBroadcastUtils.DELIMITER_QR_CODE)
-                    .append(BluetoothBroadcastUtils.PREFIX_BTSG_CODEC_CONFIG)
-                    .append(METADATA_START).append(audioCodec).append(METADATA_END)
-                    .append(BluetoothBroadcastUtils.DELIMITER_QR_CODE)
-                    .append(BluetoothBroadcastUtils.PREFIX_BTSG_AUDIO_CONTENT)
-                    .append(METADATA_START).append(audioContent).append(METADATA_END)
-                    .append(BluetoothBroadcastUtils.DELIMITER_QR_CODE)
-                    .append(BluetoothBroadcastUtils.PREFIX_BTSG_CHANNEL_PREF)
-                    .append(METADATA_START).append(hasChannelPreference).append(METADATA_END)
-                    .append(BluetoothBroadcastUtils.DELIMITER_QR_CODE)
-                    .append(BluetoothBroadcastUtils.PREFIX_BTSG_BROADCAST_CHANNEL)
-                    .append(METADATA_START).append(channels).append(METADATA_END)
-                    .append(BluetoothBroadcastUtils.DELIMITER_QR_CODE)
-                    .toString();
-            subgroupListBuilder.append(subgroupString);
-        }
-        return subgroupListBuilder.toString();
-    }
-
-    private String convertAudioCodecConfigToString(BluetoothLeAudioCodecConfigMetadata config) {
-        String audioLocation = String.valueOf(config.getAudioLocation());
-        String rawMetadata = new String(config.getRawMetadata(), StandardCharsets.UTF_8);
-        return new StringBuilder()
-            .append(BluetoothBroadcastUtils.PREFIX_BTCC_AUDIO_LOCATION)
-            .append(METADATA_START).append(audioLocation).append(METADATA_END)
-            .append(BluetoothBroadcastUtils.DELIMITER_QR_CODE)
-            .append(BluetoothBroadcastUtils.PREFIX_BTCC_RAW_METADATA)
-            .append(METADATA_START).append(rawMetadata).append(METADATA_END)
-            .append(BluetoothBroadcastUtils.DELIMITER_QR_CODE)
-            .toString();
-    }
-
-    private String convertAudioContentToString(BluetoothLeAudioContentMetadata audioContent) {
-        String rawMetadata = new String(audioContent.getRawMetadata(), StandardCharsets.UTF_8);
-        return new StringBuilder()
-            .append(BluetoothBroadcastUtils.PREFIX_BTAC_PROGRAM_INFO)
-            .append(METADATA_START).append(audioContent.getProgramInfo()).append(METADATA_END)
-            .append(BluetoothBroadcastUtils.DELIMITER_QR_CODE)
-            .append(BluetoothBroadcastUtils.PREFIX_BTAC_LANGUAGE)
-            .append(METADATA_START).append(audioContent.getLanguage()).append(METADATA_END)
-            .append(BluetoothBroadcastUtils.DELIMITER_QR_CODE)
-            .append(BluetoothBroadcastUtils.PREFIX_BTAC_RAW_METADATA)
-            .append(METADATA_START).append(rawMetadata).append(METADATA_END)
-            .append(BluetoothBroadcastUtils.DELIMITER_QR_CODE)
-            .toString();
-    }
-
-    private String convertChannelToString(List<BluetoothLeBroadcastChannel> channelList) {
-        StringBuilder channelListBuilder = new StringBuilder();
-        String channelString = "";
-        for (BluetoothLeBroadcastChannel channel: channelList) {
-            String channelAudioCodec = convertAudioCodecConfigToString(channel.getCodecMetadata());
-            channelString = new StringBuilder()
-                .append(BluetoothBroadcastUtils.PREFIX_BTBC_CHANNEL_INDEX)
-                .append(METADATA_START).append(channel.getChannelIndex()).append(METADATA_END)
-                .append(BluetoothBroadcastUtils.DELIMITER_QR_CODE)
-                .append(BluetoothBroadcastUtils.PREFIX_BTBC_CODEC_CONFIG)
-                .append(METADATA_START).append(channelAudioCodec).append(METADATA_END)
-                .append(BluetoothBroadcastUtils.DELIMITER_QR_CODE)
-                .toString();
-            channelListBuilder.append(channelString);
-        }
-        return channelListBuilder.toString();
-    }
-
-    /**
-     * Example : prefix is with the “BT:”, and end by the Android Version.
-     * BT:T:<1>;D:<00:11:22:AA:BB:CC>;AS:<1>;B:…;V:T;;
-     *
-     * @return BluetoothLeBroadcastMetadata
-     */
-    public BluetoothLeBroadcastMetadata convertToBroadcastMetadata(String qrCodeString) {
-        if (DEBUG) {
-            Log.d(TAG, "Convert " + qrCodeString + "to BluetoothLeBroadcastMetadata");
-        }
-
-        Pattern pattern = Pattern.compile(PATTERN_BT_BROADCAST_METADATA);
-        Matcher match = pattern.matcher(qrCodeString);
-        if (match.find()) {
-            try {
-                mSourceAddressType = Integer.parseInt(match.group(MATCH_INDEX_ADDRESS_TYPE));
-                mSourceDevice = BluetoothAdapter.getDefaultAdapter().getRemoteDevice(
-                    match.group(MATCH_INDEX_DEVICE));
-                mSourceAdvertisingSid = Integer.parseInt(
-                    match.group(MATCH_INDEX_ADVERTISING_SID));
-                mBroadcastId = Integer.parseInt(match.group(MATCH_INDEX_BROADCAST_ID));
-                mPaSyncInterval = Integer.parseInt(match.group(MATCH_INDEX_SYNC_INTERVAL));
-                mIsEncrypted = Boolean.valueOf(match.group(MATCH_INDEX_IS_ENCRYPTED));
-                mBroadcastCode = match.group(MATCH_INDEX_BROADCAST_CODE).getBytes();
-                mPresentationDelayMicros =
-                    Integer.parseInt(match.group(MATCH_INDEX_PRESENTATION_DELAY));
-
-                if (DEBUG) {
-                    Log.d(TAG, "Converted qrCodeString result: "
-                        + " ,Type = " + mSourceAddressType
-                        + " ,Device = " + mSourceDevice
-                        + " ,AdSid = " + mSourceAdvertisingSid
-                        + " ,BroadcastId = " + mBroadcastId
-                        + " ,paSync = " + mPaSyncInterval
-                        + " ,encrypted = " + mIsEncrypted
-                        + " ,BroadcastCode = " + Arrays.toString(mBroadcastCode)
-                        + " ,delay = " + mPresentationDelayMicros);
-                }
-
-                mSubgroup = convertToSubgroup(match.group(MATCH_INDEX_SUBGROUPS));
-
-                return new BluetoothLeBroadcastMetadata.Builder()
-                    .setSourceDevice(mSourceDevice, mSourceAddressType)
-                    .setSourceAdvertisingSid(mSourceAdvertisingSid)
-                    .setBroadcastId(mBroadcastId)
-                    .setPaSyncInterval(mPaSyncInterval)
-                    .setEncrypted(mIsEncrypted)
-                    .setBroadcastCode(mBroadcastCode)
-                    .setPresentationDelayMicros(mPresentationDelayMicros)
-                    .addSubgroup(mSubgroup)
-                    .build();
-            } catch (IllegalArgumentException e) {
-                Log.d(TAG, "IllegalArgumentException when convert : " + e);
-                return null;
-            }
-        } else {
-            if (DEBUG) {
-                Log.d(TAG, "The match fail, can not convert it to BluetoothLeBroadcastMetadata.");
-            }
-            return null;
-        }
-    }
-
-    private BluetoothLeBroadcastSubgroup convertToSubgroup(String subgroupString) {
-        if (DEBUG) {
-            Log.d(TAG, "Convert " + subgroupString + "to BluetoothLeBroadcastSubgroup");
-        }
-        Pattern pattern = Pattern.compile(PATTERN_BT_SUBGROUP);
-        Matcher match = pattern.matcher(subgroupString);
-        if (match.find()) {
-            mCodecId = Integer.parseInt(match.group(MATCH_INDEX_CODEC_ID));
-            mConfigMetadata = convertToConfigMetadata(match.group(MATCH_INDEX_CODEC_CONFIG));
-            mContentMetadata = convertToContentMetadata(match.group(MATCH_INDEX_AUDIO_CONTENT));
-            mNoChannelPreference = Boolean.valueOf(match.group(MATCH_INDEX_CHANNEL_PREF));
-            mChannel =
-                  convertToChannel(match.group(MATCH_INDEX_BROADCAST_CHANNEL), mConfigMetadata);
-
-            BluetoothLeBroadcastSubgroup.Builder subgroupBuilder =
-                    new BluetoothLeBroadcastSubgroup.Builder();
-            subgroupBuilder.setCodecId(mCodecId);
-            subgroupBuilder.setCodecSpecificConfig(mConfigMetadata);
-            subgroupBuilder.setContentMetadata(mContentMetadata);
-
-            for (BluetoothLeBroadcastChannel channel : mChannel) {
-                subgroupBuilder.addChannel(channel);
-            }
-            return subgroupBuilder.build();
-        } else {
-            if (DEBUG) {
-                Log.d(TAG,
-                        "The match fail, can not convert it to BluetoothLeBroadcastSubgroup.");
-            }
-            return null;
-        }
-    }
-
-    private BluetoothLeAudioCodecConfigMetadata convertToConfigMetadata(
-            String configMetadataString) {
-        if (DEBUG) {
-            Log.d(TAG,
-                    "Convert " + configMetadataString + "to BluetoothLeAudioCodecConfigMetadata");
-        }
-        Pattern pattern = Pattern.compile(PATTERN_REGEX);
-        Matcher match = pattern.matcher(configMetadataString);
-        ArrayList<String> resultList = new ArrayList<>();
-        while (match.find()) {
-            resultList.add(match.group(1));
-            Log.d(TAG, "Codec Config match : " + match.group(1));
-        }
-        if (DEBUG) {
-            Log.d(TAG, "Converted configMetadataString result: " + resultList.size());
-        }
-        if (resultList.size() > 0) {
-            mAudioLocation = Long.parseLong(resultList.get(LIST_INDEX_AUDIO_LOCATION));
-            mCodecConfigMetadata = resultList.get(LIST_INDEX_CODEC_CONFIG_RAW_METADATA).getBytes();
-            return new BluetoothLeAudioCodecConfigMetadata.Builder()
-                    .setAudioLocation(mAudioLocation)
-                    .build();
-        } else {
-            if (DEBUG) {
-                Log.d(TAG,
-                        "The match fail, can not convert it to "
-                                + "BluetoothLeAudioCodecConfigMetadata.");
-            }
-            return null;
-        }
-    }
-
-    private BluetoothLeAudioContentMetadata convertToContentMetadata(String contentMetadataString) {
-        if (DEBUG) {
-            Log.d(TAG, "Convert " + contentMetadataString + "to BluetoothLeAudioContentMetadata");
-        }
-        Pattern pattern = Pattern.compile(PATTERN_REGEX);
-        Matcher match = pattern.matcher(contentMetadataString);
-        ArrayList<String> resultList = new ArrayList<>();
-        while (match.find()) {
-            Log.d(TAG, "Audio Content match : " + match.group(1));
-            resultList.add(match.group(1));
-        }
-        if (DEBUG) {
-            Log.d(TAG, "Converted contentMetadataString result: " + resultList.size());
-        }
-        if (resultList.size() > 0) {
-            mProgramInfo = resultList.get(LIST_INDEX_PROGRAM_INFO);
-            mLanguage = resultList.get(LIST_INDEX_LANGUAGE);
-            mAudioContentMetadata =
-                  resultList.get(LIST_INDEX_AUDIO_CONTENT_RAW_METADATA).getBytes();
-
-            /* TODO(b/265253566) : Need to set the default value for language when the user starts
-            *  the broadcast.
-            */
-            if (mLanguage.equals("null")) {
-                mLanguage = "eng";
-            }
-
-            return new BluetoothLeAudioContentMetadata.Builder()
-                    .setProgramInfo(mProgramInfo)
-                    .setLanguage(mLanguage)
-                    .build();
-        } else {
-            if (DEBUG) {
-                Log.d(TAG,
-                        "The match fail, can not convert it to BluetoothLeAudioContentMetadata.");
-            }
-            return null;
-        }
-    }
-
-    private List<BluetoothLeBroadcastChannel> convertToChannel(String channelString,
-            BluetoothLeAudioCodecConfigMetadata configMetadata) {
-        if (DEBUG) {
-            Log.d(TAG, "Convert " + channelString + "to BluetoothLeBroadcastChannel");
-        }
-        Pattern pattern = Pattern.compile(PATTERN_BT_CHANNEL);
-        Matcher match = pattern.matcher(channelString);
-        Map<Integer, BluetoothLeAudioCodecConfigMetadata> channel =
-                new HashMap<Integer, BluetoothLeAudioCodecConfigMetadata>();
-        while (match.find()) {
-            channel.put(Integer.parseInt(match.group(MATCH_INDEX_CHANNEL_INDEX)),
-                    convertToConfigMetadata(match.group(MATCH_INDEX_CHANNEL_CODEC_CONFIG)));
-        }
-
-        if (channel.size() > 0) {
-            mIsSelected = false;
-            ArrayList<BluetoothLeBroadcastChannel> broadcastChannelList = new ArrayList<>();
-            for (Map.Entry<Integer, BluetoothLeAudioCodecConfigMetadata> entry :
-                    channel.entrySet()) {
-
-                broadcastChannelList.add(
-                        new BluetoothLeBroadcastChannel.Builder()
-                            .setSelected(mIsSelected)
-                            .setChannelIndex(entry.getKey())
-                            .setCodecMetadata(entry.getValue())
-                            .build());
-            }
-            return broadcastChannelList;
-        } else {
-            if (DEBUG) {
-                Log.d(TAG,
-                        "The match fail, can not convert it to BluetoothLeBroadcastChannel.");
-            }
-            return null;
-        }
-    }
-}
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcastMetadata.kt b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcastMetadata.kt
new file mode 100644
index 0000000..870ea8d
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcastMetadata.kt
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.settingslib.bluetooth
+
+import android.bluetooth.BluetoothLeBroadcastMetadata
+import com.android.settingslib.bluetooth.BluetoothLeBroadcastMetadataExt.toQrCodeString
+
+@Deprecated("Replace with BluetoothLeBroadcastMetadataExt")
+class LocalBluetoothLeBroadcastMetadata(private val metadata: BluetoothLeBroadcastMetadata?) {
+
+    constructor() : this(null)
+
+    fun convertToQrCodeString(): String = metadata?.toQrCodeString() ?: ""
+
+    fun convertToBroadcastMetadata(qrCodeString: String) =
+        BluetoothLeBroadcastMetadataExt.convertToBroadcastMetadata(qrCodeString)
+}
\ No newline at end of file
diff --git a/packages/SettingsLib/src/com/android/settingslib/fuelgauge/BatteryStatus.java b/packages/SettingsLib/src/com/android/settingslib/fuelgauge/BatteryStatus.java
index 76556639..1251b0d 100644
--- a/packages/SettingsLib/src/com/android/settingslib/fuelgauge/BatteryStatus.java
+++ b/packages/SettingsLib/src/com/android/settingslib/fuelgauge/BatteryStatus.java
@@ -196,6 +196,9 @@
 
     /** Gets the battery level from the intent. */
     public static int getBatteryLevel(Intent batteryChangedIntent) {
+        if (batteryChangedIntent == null) {
+            return -1; /*invalid battery level*/
+        }
         final int level = batteryChangedIntent.getIntExtra(BatteryManager.EXTRA_LEVEL, -1);
         final int scale = batteryChangedIntent.getIntExtra(BatteryManager.EXTRA_SCALE, 0);
         return scale == 0
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/PhoneMediaDevice.java b/packages/SettingsLib/src/com/android/settingslib/media/PhoneMediaDevice.java
index 1c82be9..34519c9 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/PhoneMediaDevice.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/PhoneMediaDevice.java
@@ -70,13 +70,17 @@
                 name = mContext.getString(R.string.media_transfer_wired_usb_device_name);
                 break;
             case TYPE_DOCK:
-            case TYPE_HDMI:
-                name = mRouteInfo.getName();
+                name = mContext.getString(R.string.media_transfer_dock_speaker_device_name);
                 break;
             case TYPE_BUILTIN_SPEAKER:
-            default:
                 name = mContext.getString(R.string.media_transfer_this_device_name);
                 break;
+            case TYPE_HDMI:
+                name = mContext.getString(R.string.media_transfer_external_device_name);
+                break;
+            default:
+                name = mContext.getString(R.string.media_transfer_default_device_name);
+                break;
         }
         return name.toString();
     }
diff --git a/packages/SettingsLib/src/com/android/settingslib/mobile/dataservice/SubscriptionInfoDao.java b/packages/SettingsLib/src/com/android/settingslib/mobile/dataservice/SubscriptionInfoDao.java
index e835125..e6b1cfb 100644
--- a/packages/SettingsLib/src/com/android/settingslib/mobile/dataservice/SubscriptionInfoDao.java
+++ b/packages/SettingsLib/src/com/android/settingslib/mobile/dataservice/SubscriptionInfoDao.java
@@ -16,14 +16,13 @@
 
 package com.android.settingslib.mobile.dataservice;
 
-import java.util.List;
-
 import androidx.lifecycle.LiveData;
 import androidx.room.Dao;
 import androidx.room.Insert;
 import androidx.room.OnConflictStrategy;
 import androidx.room.Query;
-import androidx.room.Update;
+
+import java.util.List;
 
 @Dao
 public interface SubscriptionInfoDao {
@@ -32,7 +31,9 @@
     void insertSubsInfo(SubscriptionInfoEntity... subscriptionInfo);
 
     @Query("SELECT * FROM " + DataServiceUtils.SubscriptionInfoData.TABLE_NAME + " ORDER BY "
-            + DataServiceUtils.SubscriptionInfoData.COLUMN_ID)
+            + " CASE WHEN " +  DataServiceUtils.SubscriptionInfoData.COLUMN_SIM_SLOT_INDEX
+            + " >= 0 THEN 1 ELSE 2 END , "
+            + DataServiceUtils.SubscriptionInfoData.COLUMN_SIM_SLOT_INDEX)
     LiveData<List<SubscriptionInfoEntity>> queryAvailableSubInfos();
 
     @Query("SELECT * FROM " + DataServiceUtils.SubscriptionInfoData.TABLE_NAME + " WHERE "
@@ -43,7 +44,8 @@
             + DataServiceUtils.SubscriptionInfoData.COLUMN_IS_ACTIVE_SUBSCRIPTION_ID
             + " = :isActiveSubscription" + " AND "
             + DataServiceUtils.SubscriptionInfoData.COLUMN_IS_SUBSCRIPTION_VISIBLE
-            + " = :isSubscriptionVisible")
+            + " = :isSubscriptionVisible" + " ORDER BY "
+            + DataServiceUtils.SubscriptionInfoData.COLUMN_SIM_SLOT_INDEX)
     LiveData<List<SubscriptionInfoEntity>> queryActiveSubInfos(
             boolean isActiveSubscription, boolean isSubscriptionVisible);
 
diff --git a/packages/SettingsLib/tests/unit/Android.bp b/packages/SettingsLib/tests/unit/Android.bp
new file mode 100644
index 0000000..a4558f1
--- /dev/null
+++ b/packages/SettingsLib/tests/unit/Android.bp
@@ -0,0 +1,35 @@
+//
+// Copyright (C) 2023 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+package {
+    default_applicable_licenses: ["frameworks_base_license"],
+}
+
+android_test {
+    name: "SettingsLibUnitTests",
+    test_suites: ["device-tests"],
+
+    srcs: [
+        "src/**/*.kt",
+    ],
+
+    static_libs: [
+        "SettingsLib",
+        "androidx.test.ext.junit",
+        "androidx.test.runner",
+        "truth-prebuilt",
+    ],
+}
diff --git a/packages/SettingsLib/tests/unit/AndroidManifest.xml b/packages/SettingsLib/tests/unit/AndroidManifest.xml
new file mode 100644
index 0000000..568f9cb
--- /dev/null
+++ b/packages/SettingsLib/tests/unit/AndroidManifest.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  Copyright (C) 2023 The Android Open Source Project
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+          package="com.android.settingslib.test">
+    <application>
+        <uses-library android:name="android.test.runner" />
+    </application>
+
+    <instrumentation
+        android:name="androidx.test.runner.AndroidJUnitRunner"
+        android:label="Tests for SettingsLib"
+        android:targetPackage="com.android.settingslib.test">
+    </instrumentation>
+</manifest>
diff --git a/packages/SettingsLib/tests/unit/OWNERS b/packages/SettingsLib/tests/unit/OWNERS
new file mode 100644
index 0000000..66559252
--- /dev/null
+++ b/packages/SettingsLib/tests/unit/OWNERS
@@ -0,0 +1,2 @@
+# We do not guard tests - everyone is welcomed to contribute to tests.
+per-file *.kt=*
diff --git a/packages/SettingsLib/tests/unit/src/com/android/settingslib/bluetooth/BluetoothLeBroadcastMetadataExtTest.kt b/packages/SettingsLib/tests/unit/src/com/android/settingslib/bluetooth/BluetoothLeBroadcastMetadataExtTest.kt
new file mode 100644
index 0000000..0e3590d
--- /dev/null
+++ b/packages/SettingsLib/tests/unit/src/com/android/settingslib/bluetooth/BluetoothLeBroadcastMetadataExtTest.kt
@@ -0,0 +1,85 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.settingslib.bluetooth
+
+import android.bluetooth.BluetoothAdapter
+import android.bluetooth.BluetoothDevice
+import android.bluetooth.BluetoothLeAudioCodecConfigMetadata
+import android.bluetooth.BluetoothLeAudioContentMetadata
+import android.bluetooth.BluetoothLeBroadcastChannel
+import android.bluetooth.BluetoothLeBroadcastMetadata
+import android.bluetooth.BluetoothLeBroadcastSubgroup
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.settingslib.bluetooth.BluetoothLeBroadcastMetadataExt.toQrCodeString
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+class BluetoothLeBroadcastMetadataExtTest {
+
+    @Test
+    fun toQrCodeString() {
+        val subgroup = BluetoothLeBroadcastSubgroup.Builder().apply {
+            setCodecId(100)
+            val audioCodecConfigMetadata = BluetoothLeAudioCodecConfigMetadata.Builder().build()
+            setCodecSpecificConfig(audioCodecConfigMetadata)
+            setContentMetadata(BluetoothLeAudioContentMetadata.Builder().build())
+            addChannel(BluetoothLeBroadcastChannel.Builder().apply {
+                setChannelIndex(1000)
+                setCodecMetadata(audioCodecConfigMetadata)
+            }.build())
+        }.build()
+
+        val metadata = BluetoothLeBroadcastMetadata.Builder().apply {
+            setSourceDevice(Device, 0)
+            setSourceAdvertisingSid(1)
+            setBroadcastId(2)
+            setPaSyncInterval(3)
+            setEncrypted(true)
+            setBroadcastCode(byteArrayOf(10, 11, 12, 13))
+            setPresentationDelayMicros(4)
+            addSubgroup(subgroup)
+        }.build()
+
+        val qrCodeString = metadata.toQrCodeString()
+
+        assertThat(qrCodeString).isEqualTo(QR_CODE_STRING)
+    }
+
+    @Test
+    fun decodeAndEncodeAgain_sameString() {
+        val metadata = BluetoothLeBroadcastMetadataExt.convertToBroadcastMetadata(QR_CODE_STRING)!!
+
+        val qrCodeString = metadata.toQrCodeString()
+
+        assertThat(qrCodeString).isEqualTo(QR_CODE_STRING)
+    }
+
+    private companion object {
+        const val TEST_DEVICE_ADDRESS = "00:A1:A1:A1:A1:A1"
+
+        val Device: BluetoothDevice =
+            BluetoothAdapter.getDefaultAdapter().getRemoteDevice(TEST_DEVICE_ADDRESS)
+
+        const val QR_CODE_STRING =
+            "BT:BluetoothLeBroadcastMetadata:AAAAAAEAAAABAAAAEQAAADAAMAA6AEEAMQA6AEEAMQA6AEEAMQA6" +
+                "AEEAMQA6AEEAMQAAAAAAAAABAAAAAgAAAAMAAAABAAAABAAAAAQAAAAKCwwNBAAAAAEAAAABAAAAZAAA" +
+                "AAAAAAABAAAAAAAAAAAAAAAGAAAABgAAAAUDAAAAAAAAAAAAAAAAAAAAAAAAAQAAAP//////////AAAA" +
+                "AAAAAAABAAAAAQAAAAAAAADoAwAAAQAAAAAAAAAAAAAABgAAAAYAAAAFAwAAAAAAAAAAAAAAAAAAAAAA" +
+                "AAAAAAD/////AAAAAAAAAAA="
+    }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml
index b661ba4..476c820 100644
--- a/packages/SystemUI/AndroidManifest.xml
+++ b/packages/SystemUI/AndroidManifest.xml
@@ -176,6 +176,7 @@
     <uses-permission android:name="android.permission.FOREGROUND_SERVICE_SYSTEM_EXEMPTED" />
     <uses-permission android:name="android.permission.RECORD_AUDIO" />
     <uses-permission android:name="android.permission.CAPTURE_AUDIO_OUTPUT"/>
+    <uses-permission android:name="android.permission.USE_EXACT_ALARM"/>
 
     <!-- Assist -->
     <uses-permission android:name="android.permission.ACCESS_VOICE_INTERACTION_SERVICE" />
diff --git a/packages/SystemUI/compose/core/src/com/android/compose/animation/Easings.kt b/packages/SystemUI/compose/core/src/com/android/compose/animation/Easings.kt
new file mode 100644
index 0000000..5d09de1
--- /dev/null
+++ b/packages/SystemUI/compose/core/src/com/android/compose/animation/Easings.kt
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.compose.animation
+
+import androidx.compose.animation.core.Easing
+import androidx.core.animation.Interpolator
+import com.android.app.animation.InterpolatorsAndroidX
+
+/**
+ * Compose-compatible definition of Android motion eases, see
+ * https://carbon.googleplex.com/android-motion/pages/easing
+ */
+object Easings {
+
+    /** The standard interpolator that should be used on every normal animation */
+    val Standard = fromInterpolator(InterpolatorsAndroidX.STANDARD)
+
+    /**
+     * The standard accelerating interpolator that should be used on every regular movement of
+     * content that is disappearing e.g. when moving off screen.
+     */
+    val StandardAccelerate = fromInterpolator(InterpolatorsAndroidX.STANDARD_ACCELERATE)
+
+    /**
+     * The standard decelerating interpolator that should be used on every regular movement of
+     * content that is appearing e.g. when coming from off screen.
+     */
+    val StandardDecelerate = fromInterpolator(InterpolatorsAndroidX.STANDARD_DECELERATE)
+
+    /** The default emphasized interpolator. Used for hero / emphasized movement of content. */
+    val Emphasized = fromInterpolator(InterpolatorsAndroidX.EMPHASIZED)
+
+    /**
+     * The accelerated emphasized interpolator. Used for hero / emphasized movement of content that
+     * is disappearing e.g. when moving off screen.
+     */
+    val EmphasizedAccelerate = fromInterpolator(InterpolatorsAndroidX.EMPHASIZED_ACCELERATE)
+
+    /**
+     * The decelerating emphasized interpolator. Used for hero / emphasized movement of content that
+     * is appearing e.g. when coming from off screen
+     */
+    val EmphasizedDecelerate = fromInterpolator(InterpolatorsAndroidX.EMPHASIZED_DECELERATE)
+
+    /** The linear interpolator. */
+    val Linear = fromInterpolator(InterpolatorsAndroidX.LINEAR)
+
+    private fun fromInterpolator(source: Interpolator) = Easing { x -> source.getInterpolation(x) }
+}
diff --git a/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/scene/ui/composable/SceneModule.kt b/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/scene/ui/composable/SceneModule.kt
index 954bad56..d364374 100644
--- a/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/scene/ui/composable/SceneModule.kt
+++ b/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/scene/ui/composable/SceneModule.kt
@@ -16,6 +16,7 @@
 
 package com.android.systemui.scene.ui.composable
 
+import android.content.Context
 import com.android.systemui.bouncer.ui.composable.BouncerScene
 import com.android.systemui.bouncer.ui.viewmodel.BouncerViewModel
 import com.android.systemui.dagger.SysUISingleton
@@ -28,6 +29,7 @@
 import com.android.systemui.scene.shared.model.SceneContainerNames
 import com.android.systemui.shade.ui.composable.ShadeScene
 import com.android.systemui.shade.ui.viewmodel.ShadeSceneViewModel
+import com.android.systemui.statusbar.phone.SystemUIDialog
 import dagger.Module
 import dagger.Provides
 import javax.inject.Named
@@ -57,6 +59,7 @@
     @SysUISingleton
     @Named(SceneContainerNames.SYSTEM_UI_DEFAULT)
     fun bouncerScene(
+        @Application context: Context,
         viewModelFactory: BouncerViewModel.Factory,
     ): BouncerScene {
         return BouncerScene(
@@ -64,6 +67,7 @@
                 viewModelFactory.create(
                     containerName = SceneContainerNames.SYSTEM_UI_DEFAULT,
                 ),
+            dialogFactory = { SystemUIDialog(context) },
         )
     }
 
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerScene.kt
index 3c74ef5..240bace 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerScene.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerScene.kt
@@ -14,9 +14,16 @@
  * limitations under the License.
  */
 
+@file:OptIn(ExperimentalMaterial3Api::class)
+
 package com.android.systemui.bouncer.ui.composable
 
+import android.app.AlertDialog
+import android.app.Dialog
+import android.content.DialogInterface
 import androidx.compose.animation.Crossfade
+import androidx.compose.animation.core.snap
+import androidx.compose.animation.core.tween
 import androidx.compose.foundation.background
 import androidx.compose.foundation.layout.Arrangement
 import androidx.compose.foundation.layout.Box
@@ -26,15 +33,20 @@
 import androidx.compose.foundation.layout.padding
 import androidx.compose.material3.Button
 import androidx.compose.material3.ButtonDefaults
+import androidx.compose.material3.ExperimentalMaterial3Api
 import androidx.compose.material3.MaterialTheme
 import androidx.compose.material3.Text
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.collectAsState
 import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
 import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.res.stringResource
 import androidx.compose.ui.unit.dp
+import com.android.systemui.R
 import com.android.systemui.bouncer.ui.viewmodel.AuthMethodBouncerViewModel
 import com.android.systemui.bouncer.ui.viewmodel.BouncerViewModel
 import com.android.systemui.bouncer.ui.viewmodel.PasswordBouncerViewModel
@@ -51,6 +63,7 @@
 /** The bouncer scene displays authentication challenges like PIN, password, or pattern. */
 class BouncerScene(
     private val viewModel: BouncerViewModel,
+    private val dialogFactory: () -> AlertDialog,
 ) : ComposableScene {
     override val key = SceneKey.Bouncer
 
@@ -68,16 +81,19 @@
     override fun Content(
         containerName: String,
         modifier: Modifier,
-    ) = BouncerScene(viewModel, modifier)
+    ) = BouncerScene(viewModel, dialogFactory, modifier)
 }
 
 @Composable
 private fun BouncerScene(
     viewModel: BouncerViewModel,
+    dialogFactory: () -> AlertDialog,
     modifier: Modifier = Modifier,
 ) {
-    val message: String by viewModel.message.collectAsState()
+    val message: BouncerViewModel.MessageViewModel by viewModel.message.collectAsState()
     val authMethodViewModel: AuthMethodBouncerViewModel? by viewModel.authMethod.collectAsState()
+    val dialogMessage: String? by viewModel.throttlingDialogMessage.collectAsState()
+    var dialog: Dialog? by remember { mutableStateOf(null) }
 
     Column(
         horizontalAlignment = Alignment.CenterHorizontally,
@@ -88,9 +104,10 @@
         Crossfade(
             targetState = message,
             label = "Bouncer message",
-        ) {
+            animationSpec = if (message.isUpdateAnimated) tween() else snap(),
+        ) { message ->
             Text(
-                text = it,
+                text = message.text,
                 color = MaterialTheme.colorScheme.onSurface,
                 style = MaterialTheme.typography.bodyLarge,
             )
@@ -132,5 +149,26 @@
                 style = MaterialTheme.typography.bodyMedium,
             )
         }
+
+        if (dialogMessage != null) {
+            if (dialog == null) {
+                dialog =
+                    dialogFactory().apply {
+                        setMessage(dialogMessage)
+                        setButton(
+                            DialogInterface.BUTTON_NEUTRAL,
+                            context.getString(R.string.ok),
+                        ) { _, _ ->
+                            viewModel.onThrottlingDialogDismissed()
+                        }
+                        setCancelable(false)
+                        setCanceledOnTouchOutside(false)
+                        show()
+                    }
+            }
+        } else {
+            dialog?.dismiss()
+            dialog = null
+        }
     }
 }
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PasswordBouncer.kt b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PasswordBouncer.kt
index 4e85621..7545ff4 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PasswordBouncer.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PasswordBouncer.kt
@@ -53,6 +53,8 @@
 ) {
     val focusRequester = remember { FocusRequester() }
     val password: String by viewModel.password.collectAsState()
+    val isInputEnabled: Boolean by viewModel.isInputEnabled.collectAsState()
+    val animateFailure: Boolean by viewModel.animateFailure.collectAsState()
 
     LaunchedEffect(Unit) {
         // When the UI comes up, request focus on the TextField to bring up the software keyboard.
@@ -61,6 +63,13 @@
         viewModel.onShown()
     }
 
+    LaunchedEffect(animateFailure) {
+        if (animateFailure) {
+            // We don't currently have a failure animation for password, just consume it:
+            viewModel.onFailureAnimationShown()
+        }
+    }
+
     Column(
         horizontalAlignment = Alignment.CenterHorizontally,
         modifier = modifier,
@@ -71,6 +80,7 @@
         TextField(
             value = password,
             onValueChange = viewModel::onPasswordInputChanged,
+            enabled = isInputEnabled,
             visualTransformation = PasswordVisualTransformation(),
             singleLine = true,
             textStyle = LocalTextStyle.current.copy(textAlign = TextAlign.Center),
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PatternBouncer.kt b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PatternBouncer.kt
index 3afd33f..b3d2e35 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PatternBouncer.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PatternBouncer.kt
@@ -18,6 +18,7 @@
 
 import android.view.HapticFeedbackConstants
 import androidx.compose.animation.core.Animatable
+import androidx.compose.animation.core.AnimationVector1D
 import androidx.compose.animation.core.tween
 import androidx.compose.foundation.Canvas
 import androidx.compose.foundation.gestures.detectDragGestures
@@ -41,12 +42,15 @@
 import androidx.compose.ui.res.integerResource
 import androidx.compose.ui.unit.IntSize
 import androidx.compose.ui.unit.dp
+import com.android.compose.animation.Easings
 import com.android.internal.R
 import com.android.systemui.bouncer.ui.viewmodel.PatternBouncerViewModel
 import com.android.systemui.bouncer.ui.viewmodel.PatternDotViewModel
+import com.android.systemui.compose.modifiers.thenIf
 import kotlin.math.min
 import kotlin.math.pow
 import kotlin.math.sqrt
+import kotlinx.coroutines.coroutineScope
 import kotlinx.coroutines.launch
 
 /**
@@ -66,9 +70,9 @@
     val rowCount = viewModel.rowCount
 
     val dotColor = MaterialTheme.colorScheme.secondary
-    val dotRadius = with(LocalDensity.current) { 8.dp.toPx() }
+    val dotRadius = with(LocalDensity.current) { (DOT_DIAMETER_DP / 2).dp.toPx() }
     val lineColor = MaterialTheme.colorScheme.primary
-    val lineStrokeWidth = dotRadius * 2 + with(LocalDensity.current) { 4.dp.toPx() }
+    val lineStrokeWidth = with(LocalDensity.current) { LINE_STROKE_WIDTH_DP.dp.toPx() }
 
     var containerSize: IntSize by remember { mutableStateOf(IntSize(0, 0)) }
     val horizontalSpacing = containerSize.width / colCount
@@ -82,6 +86,9 @@
     val currentDot: PatternDotViewModel? by viewModel.currentDot.collectAsState()
     // The dots selected so far, if the user is currently dragging.
     val selectedDots: List<PatternDotViewModel> by viewModel.selectedDots.collectAsState()
+    val isInputEnabled: Boolean by viewModel.isInputEnabled.collectAsState()
+    val isAnimationEnabled: Boolean by viewModel.isPatternVisible.collectAsState()
+    val animateFailure: Boolean by viewModel.animateFailure.collectAsState()
 
     // Map of animatables for the scale of each dot, keyed by dot.
     val dotScalingAnimatables = remember(dots) { dots.associateWith { Animatable(1f) } }
@@ -96,19 +103,46 @@
     val view = LocalView.current
 
     // When the current dot is changed, we need to update our animations.
-    LaunchedEffect(currentDot) {
-        view.performHapticFeedback(
-            HapticFeedbackConstants.VIRTUAL_KEY,
-            HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING,
-        )
+    LaunchedEffect(currentDot, isAnimationEnabled) {
+        // Perform haptic feedback, but only if the current dot is not null, so we don't perform it
+        // when the UI first shows up or when the user lifts their pointer/finger.
+        if (currentDot != null) {
+            view.performHapticFeedback(
+                HapticFeedbackConstants.VIRTUAL_KEY,
+                HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING,
+            )
+        }
 
-        // Make sure that the current dot is scaled up while the other dots are scaled back down.
+        if (!isAnimationEnabled) {
+            return@LaunchedEffect
+        }
+
+        // Make sure that the current dot is scaled up while the other dots are scaled back
+        // down.
         dotScalingAnimatables.entries.forEach { (dot, animatable) ->
             val isSelected = dot == currentDot
-            launch {
-                animatable.animateTo(if (isSelected) 2f else 1f)
+            // Launch using the longer-lived scope because we want these animations to proceed to
+            // completion even if the LaunchedEffect is canceled because its key objects have
+            // changed.
+            scope.launch {
                 if (isSelected) {
-                    animatable.animateTo(1f)
+                    animatable.animateTo(
+                        targetValue = (SELECTED_DOT_DIAMETER_DP / DOT_DIAMETER_DP.toFloat()),
+                        animationSpec =
+                            tween(
+                                durationMillis = SELECTED_DOT_REACTION_ANIMATION_DURATION_MS,
+                                easing = Easings.StandardAccelerate,
+                            ),
+                    )
+                } else {
+                    animatable.animateTo(
+                        targetValue = 1f,
+                        animationSpec =
+                            tween(
+                                durationMillis = SELECTED_DOT_RETRACT_ANIMATION_DURATION_MS,
+                                easing = Easings.StandardDecelerate,
+                            ),
+                    )
                 }
             }
         }
@@ -116,14 +150,18 @@
         selectedDots.forEach { dot ->
             lineFadeOutAnimatables[dot]?.let { line ->
                 if (!line.isRunning) {
+                    // Launch using the longer-lived scope because we want these animations to
+                    // proceed to completion even if the LaunchedEffect is canceled because its key
+                    // objects have changed.
                     scope.launch {
                         if (dot == currentDot) {
-                            // Reset the fade-out animation for the current dot. When the current
-                            // dot is switched, this entire code block runs again for the newly
-                            // selected dot.
+                            // Reset the fade-out animation for the current dot. When the
+                            // current dot is switched, this entire code block runs again for
+                            // the newly selected dot.
                             line.snapTo(1f)
                         } else {
-                            // For all non-current dots, make sure that the lines are fading out.
+                            // For all non-current dots, make sure that the lines are fading
+                            // out.
                             line.animateTo(
                                 targetValue = 0f,
                                 animationSpec =
@@ -139,6 +177,17 @@
         }
     }
 
+    // Show the failure animation if the user entered the wrong input.
+    LaunchedEffect(animateFailure) {
+        if (animateFailure) {
+            showFailureAnimation(
+                dots = dots,
+                scalingAnimatables = dotScalingAnimatables,
+            )
+            viewModel.onFailureAnimationShown()
+        }
+    }
+
     // This is the position of the input pointer.
     var inputPosition: Offset? by remember { mutableStateOf(null) }
 
@@ -148,27 +197,34 @@
             // when it leaves the bounds of the dot grid.
             .clipToBounds()
             .onSizeChanged { containerSize = it }
-            .pointerInput(Unit) {
-                detectDragGestures(
-                    onDragStart = { start ->
-                        inputPosition = start
-                        viewModel.onDragStart()
-                    },
-                    onDragEnd = {
-                        inputPosition = null
-                        lineFadeOutAnimatables.values.forEach { animatable ->
-                            scope.launch { animatable.animateTo(1f) }
-                        }
-                        viewModel.onDragEnd()
-                    },
-                ) { change, _ ->
-                    inputPosition = change.position
-                    viewModel.onDrag(
-                        xPx = change.position.x,
-                        yPx = change.position.y,
-                        containerSizePx = containerSize.width,
-                        verticalOffsetPx = verticalOffset,
-                    )
+            .thenIf(isInputEnabled) {
+                Modifier.pointerInput(Unit) {
+                    detectDragGestures(
+                        onDragStart = { start ->
+                            inputPosition = start
+                            viewModel.onDragStart()
+                        },
+                        onDragEnd = {
+                            inputPosition = null
+                            if (isAnimationEnabled) {
+                                lineFadeOutAnimatables.values.forEach { animatable ->
+                                    // Launch using the longer-lived scope because we want these
+                                    // animations to proceed to completion even if the surrounding
+                                    // scope is canceled.
+                                    scope.launch { animatable.animateTo(1f) }
+                                }
+                            }
+                            viewModel.onDragEnd()
+                        },
+                    ) { change, _ ->
+                        inputPosition = change.position
+                        viewModel.onDrag(
+                            xPx = change.position.x,
+                            yPx = change.position.y,
+                            containerSizePx = containerSize.width,
+                            verticalOffsetPx = verticalOffset,
+                        )
+                    }
                 }
             }
     ) {
@@ -247,3 +303,62 @@
     // farther the user input pointer goes from the line, the more opaque the line gets.
     return ((lineLength / gridSpacing - 0.3f) * 4f).coerceIn(0f, 1f)
 }
+
+private suspend fun showFailureAnimation(
+    dots: List<PatternDotViewModel>,
+    scalingAnimatables: Map<PatternDotViewModel, Animatable<Float, AnimationVector1D>>,
+) {
+    val dotsByRow =
+        buildList<MutableList<PatternDotViewModel>> {
+            dots.forEach { dot ->
+                val rowIndex = dot.y
+                while (size <= rowIndex) {
+                    add(mutableListOf())
+                }
+                get(rowIndex).add(dot)
+            }
+        }
+
+    coroutineScope {
+        dotsByRow.forEachIndexed { rowIndex, rowDots ->
+            rowDots.forEach { dot ->
+                scalingAnimatables[dot]?.let { dotScaleAnimatable ->
+                    launch {
+                        dotScaleAnimatable.animateTo(
+                            targetValue =
+                                FAILURE_ANIMATION_DOT_DIAMETER_DP / DOT_DIAMETER_DP.toFloat(),
+                            animationSpec =
+                                tween(
+                                    durationMillis =
+                                        FAILURE_ANIMATION_DOT_SHRINK_ANIMATION_DURATION_MS,
+                                    delayMillis =
+                                        rowIndex * FAILURE_ANIMATION_DOT_SHRINK_STAGGER_DELAY_MS,
+                                    easing = Easings.Linear,
+                                ),
+                        )
+
+                        dotScaleAnimatable.animateTo(
+                            targetValue = 1f,
+                            animationSpec =
+                                tween(
+                                    durationMillis =
+                                        FAILURE_ANIMATION_DOT_REVERT_ANIMATION_DURATION,
+                                    easing = Easings.Standard,
+                                ),
+                        )
+                    }
+                }
+            }
+        }
+    }
+}
+
+private const val DOT_DIAMETER_DP = 16
+private const val SELECTED_DOT_DIAMETER_DP = 24
+private const val SELECTED_DOT_REACTION_ANIMATION_DURATION_MS = 83
+private const val SELECTED_DOT_RETRACT_ANIMATION_DURATION_MS = 750
+private const val LINE_STROKE_WIDTH_DP = 16
+private const val FAILURE_ANIMATION_DOT_DIAMETER_DP = 13
+private const val FAILURE_ANIMATION_DOT_SHRINK_ANIMATION_DURATION_MS = 50
+private const val FAILURE_ANIMATION_DOT_SHRINK_STAGGER_DELAY_MS = 33
+private const val FAILURE_ANIMATION_DOT_REVERT_ANIMATION_DURATION = 617
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PinBouncer.kt b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PinBouncer.kt
index 9c210c2..20b859a 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PinBouncer.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PinBouncer.kt
@@ -18,11 +18,15 @@
 
 package com.android.systemui.bouncer.ui.composable
 
+import android.view.HapticFeedbackConstants
 import androidx.compose.animation.AnimatedVisibility
 import androidx.compose.animation.ExperimentalAnimationApi
 import androidx.compose.animation.animateColorAsState
 import androidx.compose.animation.animateContentSize
+import androidx.compose.animation.core.AnimationSpec
+import androidx.compose.animation.core.LinearEasing
 import androidx.compose.animation.core.animateDpAsState
+import androidx.compose.animation.core.tween
 import androidx.compose.animation.fadeIn
 import androidx.compose.animation.fadeOut
 import androidx.compose.animation.scaleIn
@@ -48,6 +52,7 @@
 import androidx.compose.runtime.getValue
 import androidx.compose.runtime.mutableStateOf
 import androidx.compose.runtime.remember
+import androidx.compose.runtime.rememberCoroutineScope
 import androidx.compose.runtime.setValue
 import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
@@ -55,15 +60,23 @@
 import androidx.compose.ui.geometry.CornerRadius
 import androidx.compose.ui.graphics.Color
 import androidx.compose.ui.input.pointer.pointerInput
+import androidx.compose.ui.platform.LocalView
 import androidx.compose.ui.unit.Dp
 import androidx.compose.ui.unit.dp
+import com.android.compose.animation.Easings
 import com.android.compose.grid.VerticalGrid
 import com.android.systemui.R
 import com.android.systemui.bouncer.ui.viewmodel.PinBouncerViewModel
 import com.android.systemui.common.shared.model.ContentDescription
 import com.android.systemui.common.shared.model.Icon
 import com.android.systemui.common.ui.compose.Icon
+import com.android.systemui.compose.modifiers.thenIf
 import kotlin.math.max
+import kotlin.time.Duration.Companion.milliseconds
+import kotlin.time.DurationUnit
+import kotlinx.coroutines.async
+import kotlinx.coroutines.delay
+import kotlinx.coroutines.launch
 
 @Composable
 internal fun PinBouncer(
@@ -75,6 +88,16 @@
 
     // The length of the PIN input received so far, so we know how many dots to render.
     val pinLength: Pair<Int, Int> by viewModel.pinLengths.collectAsState()
+    val isInputEnabled: Boolean by viewModel.isInputEnabled.collectAsState()
+    val animateFailure: Boolean by viewModel.animateFailure.collectAsState()
+
+    // Show the failure animation if the user entered the wrong input.
+    LaunchedEffect(animateFailure) {
+        if (animateFailure) {
+            showFailureAnimation()
+            viewModel.onFailureAnimationShown()
+        }
+    }
 
     Column(
         horizontalAlignment = Alignment.CenterHorizontally,
@@ -116,6 +139,7 @@
                 val digit = index + 1
                 PinButton(
                     onClicked = { viewModel.onPinButtonClicked(digit) },
+                    isEnabled = isInputEnabled,
                 ) { contentColor ->
                     PinDigit(digit, contentColor)
                 }
@@ -124,7 +148,8 @@
             PinButton(
                 onClicked = { viewModel.onBackspaceButtonClicked() },
                 onLongPressed = { viewModel.onBackspaceButtonLongPressed() },
-                isHighlighted = true,
+                isEnabled = isInputEnabled,
+                isIconButton = true,
             ) { contentColor ->
                 PinIcon(
                     Icon.Resource(
@@ -138,13 +163,15 @@
 
             PinButton(
                 onClicked = { viewModel.onPinButtonClicked(0) },
+                isEnabled = isInputEnabled,
             ) { contentColor ->
                 PinDigit(0, contentColor)
             }
 
             PinButton(
                 onClicked = { viewModel.onAuthenticateButtonClicked() },
-                isHighlighted = true,
+                isEnabled = isInputEnabled,
+                isIconButton = true,
             ) { contentColor ->
                 PinIcon(
                     Icon.Resource(
@@ -187,61 +214,107 @@
 @Composable
 private fun PinButton(
     onClicked: () -> Unit,
+    isEnabled: Boolean,
     modifier: Modifier = Modifier,
     onLongPressed: (() -> Unit)? = null,
-    isHighlighted: Boolean = false,
+    isIconButton: Boolean = false,
     content: @Composable (contentColor: Color) -> Unit,
 ) {
     var isPressed: Boolean by remember { mutableStateOf(false) }
+
+    val view = LocalView.current
+    LaunchedEffect(isPressed) {
+        if (isPressed) {
+            view.performHapticFeedback(
+                HapticFeedbackConstants.VIRTUAL_KEY,
+                HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING,
+            )
+        }
+    }
+
+    // Pin button animation specification is asymmetric: fast animation to the pressed state, and a
+    // slow animation upon release. Note that isPressed is guaranteed to be true for at least the
+    // press animation duration (see below in detectTapGestures).
+    val animEasing = if (isPressed) pinButtonPressedEasing else pinButtonReleasedEasing
+    val animDurationMillis =
+        (if (isPressed) pinButtonPressedDuration else pinButtonReleasedDuration).toInt(
+            DurationUnit.MILLISECONDS
+        )
+
     val cornerRadius: Dp by
         animateDpAsState(
-            if (isPressed) 24.dp else PinButtonSize / 2,
+            if (isPressed) 24.dp else pinButtonSize / 2,
             label = "PinButton round corners",
+            animationSpec = tween(animDurationMillis, easing = animEasing)
         )
+    val colorAnimationSpec: AnimationSpec<Color> = tween(animDurationMillis, easing = animEasing)
     val containerColor: Color by
         animateColorAsState(
             when {
-                isPressed -> MaterialTheme.colorScheme.primaryContainer
-                isHighlighted -> MaterialTheme.colorScheme.secondaryContainer
-                else -> MaterialTheme.colorScheme.surface
+                isPressed -> MaterialTheme.colorScheme.primary
+                isIconButton -> MaterialTheme.colorScheme.secondaryContainer
+                else -> MaterialTheme.colorScheme.surfaceVariant
             },
             label = "Pin button container color",
+            animationSpec = colorAnimationSpec
         )
     val contentColor: Color by
         animateColorAsState(
             when {
-                isPressed -> MaterialTheme.colorScheme.onPrimaryContainer
-                isHighlighted -> MaterialTheme.colorScheme.onSecondaryContainer
-                else -> MaterialTheme.colorScheme.onSurface
+                isPressed -> MaterialTheme.colorScheme.onPrimary
+                isIconButton -> MaterialTheme.colorScheme.onSecondaryContainer
+                else -> MaterialTheme.colorScheme.onSurfaceVariant
             },
             label = "Pin button container color",
+            animationSpec = colorAnimationSpec
         )
 
+    val scope = rememberCoroutineScope()
+
     Box(
         contentAlignment = Alignment.Center,
         modifier =
             modifier
-                .size(PinButtonSize)
+                .size(pinButtonSize)
                 .drawBehind {
                     drawRoundRect(
                         color = containerColor,
                         cornerRadius = CornerRadius(cornerRadius.toPx()),
                     )
                 }
-                .pointerInput(Unit) {
-                    detectTapGestures(
-                        onPress = {
-                            isPressed = true
-                            tryAwaitRelease()
-                            isPressed = false
-                        },
-                        onTap = { onClicked() },
-                        onLongPress = onLongPressed?.let { { onLongPressed() } },
-                    )
+                .thenIf(isEnabled) {
+                    Modifier.pointerInput(Unit) {
+                        detectTapGestures(
+                            onPress = {
+                                scope.launch {
+                                    isPressed = true
+                                    val minDuration = async {
+                                        delay(pinButtonPressedDuration + pinButtonHoldTime)
+                                    }
+                                    tryAwaitRelease()
+                                    minDuration.await()
+                                    isPressed = false
+                                }
+                            },
+                            onTap = { onClicked() },
+                            onLongPress = onLongPressed?.let { { onLongPressed() } },
+                        )
+                    }
                 },
     ) {
         content(contentColor)
     }
 }
 
-private val PinButtonSize = 84.dp
+private fun showFailureAnimation() {
+    // TODO(b/282730134): implement.
+}
+
+private val pinButtonSize = 84.dp
+
+// Pin button motion spec: http://shortn/_9TTIG6SoEa
+private val pinButtonPressedDuration = 100.milliseconds
+private val pinButtonPressedEasing = LinearEasing
+private val pinButtonHoldTime = 33.milliseconds
+private val pinButtonReleasedDuration = 420.milliseconds
+private val pinButtonReleasedEasing = Easings.Standard
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/compose/modifiers/ConditionalModifiers.kt b/packages/SystemUI/compose/features/src/com/android/systemui/compose/modifiers/ConditionalModifiers.kt
new file mode 100644
index 0000000..83071d7
--- /dev/null
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/compose/modifiers/ConditionalModifiers.kt
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.compose.modifiers
+
+import androidx.compose.ui.Modifier
+
+/**
+ * Concatenates this modifier with another if `condition` is true.
+ *
+ * @param condition Whether or not to apply the modifiers.
+ * @param factory Creates the modifier to concatenate with the current one.
+ * @return a Modifier representing this modifier followed by other in sequence.
+ * @see Modifier.then
+ *
+ * This method allows inline conditional addition of modifiers to a modifier chain. Instead of
+ * writing
+ *
+ * ```
+ * val aModifier = Modifier.a()
+ * val bModifier = if(condition) aModifier.b() else aModifier
+ * Composable(modifier = bModifier)
+ * ```
+ *
+ * You can instead write
+ *
+ * ```
+ * Composable(modifier = Modifier.a().thenIf(condition){
+ *  Modifier.b()
+ * }
+ * ```
+ *
+ * This makes the modifier chain easier to read.
+ *
+ * Note that unlike the non-factory version, the conditional modifier is recreated each time, and
+ * may never be created at all.
+ */
+inline fun Modifier.thenIf(condition: Boolean, crossinline factory: () -> Modifier): Modifier =
+    if (condition) this.then(factory()) else this
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockController.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockController.kt
index 252c8e3..e557c8e 100644
--- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockController.kt
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockController.kt
@@ -33,6 +33,7 @@
 import com.android.systemui.plugins.ClockFaceController
 import com.android.systemui.plugins.ClockFaceEvents
 import com.android.systemui.plugins.ClockSettings
+import com.android.systemui.plugins.WeatherData
 import java.io.PrintWriter
 import java.util.Locale
 import java.util.TimeZone
@@ -50,6 +51,7 @@
     private val layoutInflater: LayoutInflater,
     private val resources: Resources,
     private val settings: ClockSettings?,
+    private val hasStepClockAnimation: Boolean = false,
 ) : ClockController {
     override val smallClock: DefaultClockFaceController
     override val largeClock: LargeClockFaceController
@@ -170,7 +172,8 @@
         view: AnimatableClockView,
         seedColor: Int?,
     ) : DefaultClockFaceController(view, seedColor) {
-        override val config = ClockFaceConfig(hasCustomPositionUpdatedAnimation = true)
+        override val config =
+            ClockFaceConfig(hasCustomPositionUpdatedAnimation = hasStepClockAnimation)
 
         init {
             animations = LargeClockAnimations(view, 0f, 0f)
@@ -225,6 +228,8 @@
 
             clocks.forEach { it.refreshFormat() }
         }
+
+        override fun onWeatherDataChanged(data: WeatherData) {}
     }
 
     open inner class DefaultClockAnimations(
@@ -271,6 +276,8 @@
             // the top margin change in recomputePadding to make clock be centered
             view.translationY = 0.5f * view.bottom * (1 - swipingFraction)
         }
+
+        override fun onPositionUpdated(fromLeft: Int, direction: Int, fraction: Float) {}
     }
 
     inner class LargeClockAnimations(
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockProvider.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockProvider.kt
index 0fd1b49..949641a 100644
--- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockProvider.kt
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockProvider.kt
@@ -29,10 +29,11 @@
 const val DEFAULT_CLOCK_ID = "DEFAULT"
 
 /** Provides the default system clock */
-class DefaultClockProvider constructor(
+class DefaultClockProvider(
     val ctx: Context,
     val layoutInflater: LayoutInflater,
-    val resources: Resources
+    val resources: Resources,
+    val hasStepClockAnimation: Boolean = false
 ) : ClockProvider {
     override fun getClocks(): List<ClockMetadata> =
         listOf(ClockMetadata(DEFAULT_CLOCK_ID, DEFAULT_CLOCK_NAME))
@@ -42,7 +43,13 @@
             throw IllegalArgumentException("${settings.clockId} is unsupported by $TAG")
         }
 
-        return DefaultClockController(ctx, layoutInflater, resources, settings)
+        return DefaultClockController(
+            ctx,
+            layoutInflater,
+            resources,
+            settings,
+            hasStepClockAnimation,
+        )
     }
 
     override fun getClockThumbnail(id: ClockId): Drawable? {
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/ClockProviderPlugin.kt b/packages/SystemUI/plugin/src/com/android/systemui/plugins/ClockProviderPlugin.kt
index 5d0a3af..3ae328e 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/ClockProviderPlugin.kt
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/ClockProviderPlugin.kt
@@ -74,18 +74,10 @@
         resources: Resources,
         dozeFraction: Float,
         foldFraction: Float,
-    ) {
-        events.onColorPaletteChanged(resources)
-        smallClock.animations.doze(dozeFraction)
-        largeClock.animations.doze(dozeFraction)
-        smallClock.animations.fold(foldFraction)
-        largeClock.animations.fold(foldFraction)
-        smallClock.events.onTimeTick()
-        largeClock.events.onTimeTick()
-    }
+    )
 
     /** Optional method for dumping debug information */
-    fun dump(pw: PrintWriter) {}
+    fun dump(pw: PrintWriter)
 }
 
 /** Interface for a specific clock face version rendered by the clock */
@@ -109,37 +101,37 @@
 /** Events that should call when various rendering parameters change */
 interface ClockEvents {
     /** Call whenever timezone changes */
-    fun onTimeZoneChanged(timeZone: TimeZone) {}
+    fun onTimeZoneChanged(timeZone: TimeZone)
 
     /** Call whenever the text time format changes (12hr vs 24hr) */
-    fun onTimeFormatChanged(is24Hr: Boolean) {}
+    fun onTimeFormatChanged(is24Hr: Boolean)
 
     /** Call whenever the locale changes */
-    fun onLocaleChanged(locale: Locale) {}
+    fun onLocaleChanged(locale: Locale)
 
     /** Call whenever the color palette should update */
-    fun onColorPaletteChanged(resources: Resources) {}
+    fun onColorPaletteChanged(resources: Resources)
 
     /** Call if the seed color has changed and should be updated */
-    fun onSeedColorChanged(seedColor: Int?) {}
+    fun onSeedColorChanged(seedColor: Int?)
 
     /** Call whenever the weather data should update */
-    fun onWeatherDataChanged(data: WeatherData) {}
+    fun onWeatherDataChanged(data: WeatherData)
 }
 
 /** Methods which trigger various clock animations */
 interface ClockAnimations {
     /** Runs an enter animation (if any) */
-    fun enter() {}
+    fun enter()
 
     /** Sets how far into AOD the device currently is. */
-    fun doze(fraction: Float) {}
+    fun doze(fraction: Float)
 
     /** Sets how far into the folding animation the device is. */
-    fun fold(fraction: Float) {}
+    fun fold(fraction: Float)
 
     /** Runs the battery animation (if any). */
-    fun charge() {}
+    fun charge()
 
     /**
      * Runs when the clock's position changed during the move animation.
@@ -150,32 +142,32 @@
      * @param fraction fraction of the clock movement. 0 means it is at the beginning, and 1 means
      *   it finished moving.
      */
-    fun onPositionUpdated(fromLeft: Int, direction: Int, fraction: Float) {}
+    fun onPositionUpdated(fromLeft: Int, direction: Int, fraction: Float)
 
     /**
      * Runs when swiping clock picker, swipingFraction: 1.0 -> clock is scaled up in the preview,
      * 0.0 -> clock is scaled down in the shade; previewRatio is previewSize / screenSize
      */
-    fun onPickerCarouselSwiping(swipingFraction: Float) {}
+    fun onPickerCarouselSwiping(swipingFraction: Float)
 }
 
 /** Events that have specific data about the related face */
 interface ClockFaceEvents {
     /** Call every time tick */
-    fun onTimeTick() {}
+    fun onTimeTick()
 
     /**
      * Region Darkness specific to the clock face.
      * - isRegionDark = dark theme -> clock should be light
      * - !isRegionDark = light theme -> clock should be dark
      */
-    fun onRegionDarknessChanged(isRegionDark: Boolean) {}
+    fun onRegionDarknessChanged(isRegionDark: Boolean)
 
     /**
      * Call whenever font settings change. Pass in a target font size in pixels. The specific clock
      * design is allowed to ignore this target size on a case-by-case basis.
      */
-    fun onFontSettingChanged(fontSizePx: Float) {}
+    fun onFontSettingChanged(fontSizePx: Float)
 
     /**
      * Target region information for the clock face. For small clock, this will match the bounds of
@@ -184,7 +176,7 @@
      * render within the centered targetRect to avoid obstructing other elements. The specified
      * targetRegion is relative to the parent view.
      */
-    fun onTargetRegionChanged(targetRegion: Rect?) {}
+    fun onTargetRegionChanged(targetRegion: Rect?)
 }
 
 /** Tick rates for clocks */
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index e8b9f2a..c619d46 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -2973,9 +2973,11 @@
         <xliff:g id="weather_condition" example="Partly cloudy">%1$s</xliff:g>, <xliff:g id="temperature" example="7°C">%2$s</xliff:g>
     </string>
 
-    <!-- TODO(b/259369672): Replace with final resource. -->
     <!-- [CHAR LIMIT=30] Label used to open Note Task -->
-    <string name="note_task_button_label">Notetaking</string>
+    <string name="note_task_button_label">Note-taking</string>
+
+    <!-- [CHAR LIMIT=25] Long label used by Note Task Shortcut -->
+    <string name="note_task_shortcut_long_label">Note-taking, <xliff:g id="note_taking_app" example="Note-taking App">%1$s</xliff:g></string>
 
     <!-- [CHAR LIMIT=NONE] Le audio broadcast dialog, media app is broadcasting -->
     <string name="broadcasting_description_is_broadcasting">Broadcasting</string>
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java
index 1db0ab6..835cc13 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java
@@ -46,7 +46,6 @@
 import com.android.keyguard.logging.KeyguardLogger;
 import com.android.systemui.R;
 import com.android.systemui.flags.FeatureFlags;
-import com.android.systemui.flags.Flags;
 import com.android.systemui.plugins.ClockController;
 import com.android.systemui.statusbar.notification.AnimatableProperty;
 import com.android.systemui.statusbar.notification.PropertyAnimator;
@@ -363,7 +362,7 @@
         boolean customClockAnimation = clock != null
                 && clock.getLargeClock().getConfig().getHasCustomPositionUpdatedAnimation();
 
-        if (mFeatureFlags.isEnabled(Flags.STEP_CLOCK_ANIMATION) && customClockAnimation) {
+        if (customClockAnimation) {
             // Find the clock, so we can exclude it from this transition.
             FrameLayout clockContainerView = mView.findViewById(R.id.lockscreen_clock_view_large);
 
@@ -400,8 +399,10 @@
     @VisibleForTesting
     static class SplitShadeTransitionAdapter extends Transition {
         private static final String PROP_BOUNDS_LEFT = "splitShadeTransitionAdapter:boundsLeft";
+        private static final String PROP_BOUNDS_RIGHT = "splitShadeTransitionAdapter:boundsRight";
         private static final String PROP_X_IN_WINDOW = "splitShadeTransitionAdapter:xInWindow";
-        private static final String[] TRANSITION_PROPERTIES = { PROP_BOUNDS_LEFT, PROP_X_IN_WINDOW};
+        private static final String[] TRANSITION_PROPERTIES = {
+                PROP_BOUNDS_LEFT, PROP_BOUNDS_RIGHT, PROP_X_IN_WINDOW};
 
         private final KeyguardClockSwitchController mController;
 
@@ -412,6 +413,7 @@
 
         private void captureValues(TransitionValues transitionValues) {
             transitionValues.values.put(PROP_BOUNDS_LEFT, transitionValues.view.getLeft());
+            transitionValues.values.put(PROP_BOUNDS_RIGHT, transitionValues.view.getRight());
             int[] locationInWindowTmp = new int[2];
             transitionValues.view.getLocationInWindow(locationInWindowTmp);
             transitionValues.values.put(PROP_X_IN_WINDOW, locationInWindowTmp[0]);
diff --git a/packages/SystemUI/src/com/android/keyguard/dagger/ClockRegistryModule.java b/packages/SystemUI/src/com/android/keyguard/dagger/ClockRegistryModule.java
index 2d0bf9c..d2b10d6 100644
--- a/packages/SystemUI/src/com/android/keyguard/dagger/ClockRegistryModule.java
+++ b/packages/SystemUI/src/com/android/keyguard/dagger/ClockRegistryModule.java
@@ -63,7 +63,11 @@
                 bgDispatcher,
                 featureFlags.isEnabled(Flags.LOCKSCREEN_CUSTOM_CLOCKS),
                 /* handleAllUsers= */ true,
-                new DefaultClockProvider(context, layoutInflater, resources),
+                new DefaultClockProvider(
+                        context,
+                        layoutInflater,
+                        resources,
+                        featureFlags.isEnabled(Flags.STEP_CLOCK_ANIMATION)),
                 context.getString(R.string.lockscreen_clock_id_fallback),
                 logBuffer,
                 /* keepAllLoaded = */ false,
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/SideFpsController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/SideFpsController.kt
index 0782537..630cfff 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/SideFpsController.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/SideFpsController.kt
@@ -152,7 +152,7 @@
         WindowManager.LayoutParams(
                 WindowManager.LayoutParams.WRAP_CONTENT,
                 WindowManager.LayoutParams.WRAP_CONTENT,
-                WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG,
+                WindowManager.LayoutParams.TYPE_NAVIGATION_BAR_PANEL,
                 Utils.FINGERPRINT_OVERLAY_LAYOUT_PARAM_FLAGS,
                 PixelFormat.TRANSLUCENT
             )
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 e462e2f..1d2fce7 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
@@ -148,12 +148,17 @@
      *
      * If the input is correct, the device will be unlocked and the lock screen and bouncer will be
      * dismissed and hidden.
+     *
+     * @param input The input from the user to try to authenticate with. This can be a list of
+     *   different things, based on the current authentication method.
+     * @return `true` if the authentication succeeded and the device is now unlocked; `false`
+     *   otherwise.
      */
     fun authenticate(
         input: List<Any>,
-    ) {
+    ): Boolean {
         if (repository.throttling.value != null) {
-            return
+            return false
         }
 
         val isAuthenticated = authenticationInteractor.authenticate(input)
@@ -186,6 +191,8 @@
                 }
             else -> repository.setMessage(errorMessage(authenticationMethod.value))
         }
+
+        return isAuthenticated
     }
 
     private fun promptMessage(authMethod: AuthenticationMethodModel): String {
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 774a559..d95b70c 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,14 +16,37 @@
 
 package com.android.systemui.bouncer.ui.viewmodel
 
+import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.asStateFlow
 
-sealed interface AuthMethodBouncerViewModel {
+sealed class 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>
+    val isInputEnabled: StateFlow<Boolean>,
+) {
+
+    private val _animateFailure = MutableStateFlow(false)
+    /**
+     * Whether a failure animation should be shown. Once consumed, the UI must call
+     * [onFailureAnimationShown] to consume this state.
+     */
+    val animateFailure: StateFlow<Boolean> = _animateFailure.asStateFlow()
+
+    /**
+     * Notifies that the failure animation has been shown. This should be called to consume a `true`
+     * value in [animateFailure].
+     */
+    fun onFailureAnimationShown() {
+        _animateFailure.value = false
+    }
+
+    /** Ask the UI to show the failure animation. */
+    protected fun showFailureAnimation() {
+        _animateFailure.value = true
+    }
 }
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 02991bd..984d9ab 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
@@ -20,6 +20,7 @@
 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.bouncer.shared.model.AuthenticationThrottledModel
 import com.android.systemui.dagger.qualifiers.Application
 import dagger.assisted.Assisted
 import dagger.assisted.AssistedFactory
@@ -29,6 +30,7 @@
 import kotlinx.coroutines.flow.SharingStarted
 import kotlinx.coroutines.flow.StateFlow
 import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.flow.combine
 import kotlinx.coroutines.flow.distinctUntilChanged
 import kotlinx.coroutines.flow.map
 import kotlinx.coroutines.flow.stateIn
@@ -45,21 +47,6 @@
 ) {
     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 }
@@ -104,13 +91,21 @@
             )
 
     /** The user-facing message to show in the bouncer. */
-    val message: StateFlow<String> =
-        interactor.message
-            .map { it ?: "" }
+    val message: StateFlow<MessageViewModel> =
+        combine(
+                interactor.message,
+                interactor.throttling,
+            ) { message, throttling ->
+                toMessageViewModel(message, throttling)
+            }
             .stateIn(
                 scope = applicationScope,
                 started = SharingStarted.WhileSubscribed(),
-                initialValue = interactor.message.value ?: "",
+                initialValue =
+                    toMessageViewModel(
+                        message = interactor.message.value,
+                        throttling = interactor.throttling.value,
+                    ),
             )
 
     private val _throttlingDialogMessage = MutableStateFlow<String?>(null)
@@ -177,6 +172,28 @@
         }
     }
 
+    private fun toMessageViewModel(
+        message: String?,
+        throttling: AuthenticationThrottledModel?,
+    ): MessageViewModel {
+        return MessageViewModel(
+            text = message ?: "",
+            isUpdateAnimated = throttling == null,
+        )
+    }
+
+    data class MessageViewModel(
+        val text: String,
+
+        /**
+         * 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 isUpdateAnimated: Boolean,
+    )
+
     @AssistedFactory
     interface Factory {
         fun create(
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 c38fcaa..55929b5 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,8 +24,11 @@
 /** Holds UI state and handles user input for the password bouncer UI. */
 class PasswordBouncerViewModel(
     private val interactor: BouncerInteractor,
-    override val isInputEnabled: StateFlow<Boolean>,
-) : AuthMethodBouncerViewModel {
+    isInputEnabled: StateFlow<Boolean>,
+) :
+    AuthMethodBouncerViewModel(
+        isInputEnabled = isInputEnabled,
+    ) {
 
     private val _password = MutableStateFlow("")
     /** The password entered so far. */
@@ -47,7 +50,10 @@
 
     /** Notifies that the user has pressed the key for attempting to authenticate the password. */
     fun onAuthenticateKeyPressed() {
-        interactor.authenticate(password.value.toCharArray().toList())
+        if (!interactor.authenticate(password.value.toCharArray().toList())) {
+            showFailureAnimation()
+        }
+
         _password.value = ""
     }
 }
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 1b0b38e..d9ef75d 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,8 +37,11 @@
     private val applicationContext: Context,
     applicationScope: CoroutineScope,
     private val interactor: BouncerInteractor,
-    override val isInputEnabled: StateFlow<Boolean>,
-) : AuthMethodBouncerViewModel {
+    isInputEnabled: StateFlow<Boolean>,
+) :
+    AuthMethodBouncerViewModel(
+        isInputEnabled = isInputEnabled,
+    ) {
 
     /** The number of columns in the dot grid. */
     val columnCount = 3
@@ -150,7 +153,11 @@
 
     /** Notifies that the user has ended the drag gesture across the dot grid. */
     fun onDragEnd() {
-        interactor.authenticate(_selectedDots.value.map { it.toCoordinate() })
+        val isSuccessfullyAuthenticated =
+            interactor.authenticate(_selectedDots.value.map { it.toCoordinate() })
+        if (!isSuccessfullyAuthenticated) {
+            showFailureAnimation()
+        }
 
         _dots.value = defaultDots()
         _currentDot.value = null
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 2a733d9..5c0fd92 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,8 +33,11 @@
 class PinBouncerViewModel(
     private val applicationScope: CoroutineScope,
     private val interactor: BouncerInteractor,
-    override val isInputEnabled: StateFlow<Boolean>,
-) : AuthMethodBouncerViewModel {
+    isInputEnabled: StateFlow<Boolean>,
+) :
+    AuthMethodBouncerViewModel(
+        isInputEnabled = isInputEnabled,
+    ) {
 
     private val entered = MutableStateFlow<List<Int>>(emptyList())
     /**
@@ -92,7 +95,10 @@
 
     /** Notifies that the user clicked the "enter" button. */
     fun onAuthenticateButtonClicked() {
-        interactor.authenticate(entered.value)
+        if (!interactor.authenticate(entered.value)) {
+            showFailureAnimation()
+        }
+
         entered.value = emptyList()
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsListingControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsListingControllerImpl.kt
index 8ba060e..1eba667 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsListingControllerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsListingControllerImpl.kt
@@ -35,6 +35,7 @@
 import com.android.systemui.flags.FeatureFlags
 import com.android.systemui.flags.Flags
 import com.android.systemui.settings.UserTracker
+import com.android.systemui.util.ActivityTaskManagerProxy
 import com.android.systemui.util.asIndenting
 import com.android.systemui.util.indentIfPossible
 import java.io.PrintWriter
@@ -67,6 +68,7 @@
     @Background private val backgroundExecutor: Executor,
     private val serviceListingBuilder: (Context) -> ServiceListing,
     private val userTracker: UserTracker,
+    private val activityTaskManagerProxy: ActivityTaskManagerProxy,
     dumpManager: DumpManager,
     private val featureFlags: FeatureFlags
 ) : ControlsListingController, Dumpable {
@@ -76,9 +78,18 @@
             context: Context,
             @Background executor: Executor,
             userTracker: UserTracker,
+            activityTaskManagerProxy: ActivityTaskManagerProxy,
             dumpManager: DumpManager,
             featureFlags: FeatureFlags
-    ) : this(context, executor, ::createServiceListing, userTracker, dumpManager, featureFlags)
+    ) : this(
+        context,
+        executor,
+        ::createServiceListing,
+        userTracker,
+        activityTaskManagerProxy,
+        dumpManager,
+        featureFlags
+    )
 
     private var serviceListing = serviceListingBuilder(context)
     // All operations in background thread
@@ -113,7 +124,8 @@
     }
 
     private fun updateServices(newServices: List<ControlsServiceInfo>) {
-        if (featureFlags.isEnabled(Flags.USE_APP_PANELS)) {
+        if (featureFlags.isEnabled(Flags.USE_APP_PANELS) &&
+                activityTaskManagerProxy.supportsMultiWindow(context)) {
             val allowAllApps = featureFlags.isEnabled(Flags.APP_PANELS_ALL_APPS_ALLOWED)
             newServices.forEach {
                 it.resolvePanelActivity(allowAllApps) }
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
index e99db14..870fff7 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
@@ -355,7 +355,8 @@
 
     // TODO(b/280426085): Tracking Bug
     @JvmField
-    val NEW_BLUETOOTH_REPOSITORY = unreleasedFlag(612, "new_bluetooth_repository")
+    val NEW_BLUETOOTH_REPOSITORY =
+            unreleasedFlag(612, "new_bluetooth_repository", teamfood = true)
 
     // 700 - dialer/calls
     // TODO(b/254512734): Tracking Bug
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceLocalUserSelectionManager.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceLocalUserSelectionManager.kt
index 356a8fb..4dad179 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceLocalUserSelectionManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceLocalUserSelectionManager.kt
@@ -47,7 +47,7 @@
 class KeyguardQuickAffordanceLocalUserSelectionManager
 @Inject
 constructor(
-    @Application context: Context,
+    @Application private val context: Context,
     private val userFileManager: UserFileManager,
     private val userTracker: UserTracker,
     broadcastDispatcher: BroadcastDispatcher,
@@ -126,6 +126,11 @@
             }
 
     override fun getSelections(): Map<String, List<String>> {
+        // If the custom shortcuts feature is not enabled, ignore prior selections and use defaults
+        if (!context.resources.getBoolean(R.bool.custom_lockscreen_shortcuts_enabled)) {
+            return defaults
+        }
+
         val slotKeys = sharedPrefs.all.keys.filter { it.startsWith(KEY_PREFIX_SLOT) }
         val result =
             slotKeys
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfig.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfig.kt
index 4ba2eb9..1227078 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfig.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfig.kt
@@ -18,7 +18,6 @@
 package com.android.systemui.keyguard.data.quickaffordance
 
 import android.content.Context
-import android.content.Intent
 import android.graphics.drawable.Drawable
 import android.service.quickaccesswallet.GetWalletCardsError
 import android.service.quickaccesswallet.GetWalletCardsResponse
@@ -33,7 +32,6 @@
 import com.android.systemui.common.shared.model.Icon
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
-import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceConfig.Companion.componentName
 import com.android.systemui.plugins.ActivityStarter
 import com.android.systemui.wallet.controller.QuickAccessWalletController
 import javax.inject.Inject
@@ -103,17 +101,6 @@
             !walletController.walletClient.isWalletServiceAvailable ->
                 KeyguardQuickAffordanceConfig.PickerScreenState.UnavailableOnDevice
             !walletController.isWalletEnabled || queryCards().isEmpty() -> {
-                val componentName =
-                    walletController.walletClient.createWalletSettingsIntent().toComponentName()
-                val actionText =
-                    if (componentName != null) {
-                        context.getString(
-                            R.string.keyguard_affordance_enablement_dialog_action_template,
-                            pickerName,
-                        )
-                    } else {
-                        null
-                    }
                 KeyguardQuickAffordanceConfig.PickerScreenState.Disabled(
                     instructions =
                         listOf(
@@ -124,8 +111,6 @@
                                 R.string.keyguard_affordance_enablement_dialog_wallet_instruction_2
                             ),
                         ),
-                    actionText = actionText,
-                    actionComponentName = componentName,
                 )
             }
             else -> KeyguardQuickAffordanceConfig.PickerScreenState.Default()
@@ -182,14 +167,6 @@
         }
     }
 
-    private fun Intent?.toComponentName(): String? {
-        if (this == null) {
-            return null
-        }
-
-        return componentName(packageName = `package`, action = action)
-    }
-
     companion object {
         private const val TAG = "QuickAccessWalletKeyguardQuickAffordanceConfig"
     }
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
index 641e20b..16ad29a 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardClockRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardClockRepository.kt
@@ -21,12 +21,17 @@
 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.plugins.ClockId
+import com.android.systemui.shared.clocks.ClockRegistry
 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.channels.awaitClose
 import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.callbackFlow
 import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.mapNotNull
 import kotlinx.coroutines.flow.onStart
 import kotlinx.coroutines.withContext
 
@@ -35,6 +40,7 @@
 @Inject
 constructor(
     private val secureSettings: SecureSettings,
+    private val clockRegistry: ClockRegistry,
     @Background private val backgroundDispatcher: CoroutineDispatcher,
 ) {
 
@@ -47,6 +53,24 @@
             .onStart { emit(Unit) } // Forces an initial update.
             .map { getClockSize() }
 
+    val currentClockId: Flow<ClockId> =
+        callbackFlow {
+                fun send() {
+                    trySend(clockRegistry.currentClockId)
+                }
+
+                val listener =
+                    object : ClockRegistry.ClockChangeListener {
+                        override fun onCurrentClockChanged() {
+                            send()
+                        }
+                    }
+                clockRegistry.registerClockChangeListener(listener)
+                send()
+                awaitClose { clockRegistry.unregisterClockChangeListener(listener) }
+            }
+            .mapNotNull { it }
+
     private suspend fun getClockSize(): SettingsClockSize {
         return withContext(backgroundDispatcher) {
             if (
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
index 98f445c..dad5831 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardClockInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardClockInteractor.kt
@@ -20,6 +20,7 @@
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.keyguard.data.repository.KeyguardClockRepository
 import com.android.systemui.keyguard.shared.model.SettingsClockSize
+import com.android.systemui.plugins.ClockId
 import javax.inject.Inject
 import kotlinx.coroutines.flow.Flow
 
@@ -31,4 +32,6 @@
     repository: KeyguardClockRepository,
 ) {
     val selectedClockSize: Flow<SettingsClockSize> = repository.selectedClockSize
+
+    val currentClockId: Flow<ClockId> = repository.currentClockId
 }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBottomAreaViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBottomAreaViewBinder.kt
index c8d37a1..a8d662c 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBottomAreaViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBottomAreaViewBinder.kt
@@ -406,19 +406,21 @@
         view.isClickable = viewModel.isClickable
         if (viewModel.isClickable) {
             if (viewModel.useLongPress) {
-                view.setOnTouchListener(
-                    KeyguardQuickAffordanceOnTouchListener(
-                        view,
-                        viewModel,
-                        messageDisplayer,
-                        vibratorHelper,
-                        falsingManager,
-                    )
+                val onTouchListener = KeyguardQuickAffordanceOnTouchListener(
+                    view,
+                    viewModel,
+                    messageDisplayer,
+                    vibratorHelper,
+                    falsingManager,
                 )
+                view.setOnTouchListener(onTouchListener)
+                view.onLongClickListener =
+                    OnLongClickListener(falsingManager, viewModel, vibratorHelper, onTouchListener)
             } else {
                 view.setOnClickListener(OnClickListener(viewModel, checkNotNull(falsingManager)))
             }
         } else {
+            view.onLongClickListener = null
             view.setOnClickListener(null)
             view.setOnTouchListener(null)
         }
@@ -454,6 +456,42 @@
             .start()
     }
 
+    private class OnLongClickListener(
+        private val falsingManager: FalsingManager?,
+        private val viewModel: KeyguardQuickAffordanceViewModel,
+        private val vibratorHelper: VibratorHelper?,
+        private val onTouchListener: KeyguardQuickAffordanceOnTouchListener
+    ) : View.OnLongClickListener {
+        override fun onLongClick(view: View): Boolean {
+            if (falsingManager?.isFalseLongTap(FalsingManager.MODERATE_PENALTY) == true) {
+                return true
+            }
+
+            if (viewModel.configKey != null) {
+                viewModel.onClicked(
+                    KeyguardQuickAffordanceViewModel.OnClickedParameters(
+                        configKey = viewModel.configKey,
+                        expandable = Expandable.fromView(view),
+                        slotId = viewModel.slotId,
+                    )
+                )
+                vibratorHelper?.vibrate(
+                    if (viewModel.isActivated) {
+                        KeyguardBottomAreaVibrations.Activated
+                    } else {
+                        KeyguardBottomAreaVibrations.Deactivated
+                    }
+                )
+            }
+
+            onTouchListener.cancel()
+            return true
+        }
+
+        override fun onLongClickUseDefaultHapticFeedback(view: View?) = false
+
+    }
+
     private class OnClickListener(
         private val viewModel: KeyguardQuickAffordanceViewModel,
         private val falsingManager: FalsingManager,
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardPreviewClockSmartspaceViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardPreviewClockViewBinder.kt
similarity index 70%
rename from packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardPreviewClockSmartspaceViewBinder.kt
rename to packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardPreviewClockViewBinder.kt
index 57c32b3..1b5b329 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardPreviewClockSmartspaceViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardPreviewClockViewBinder.kt
@@ -21,19 +21,17 @@
 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.keyguard.ui.viewmodel.KeyguardPreviewClockViewModel
 import com.android.systemui.lifecycle.repeatWhenAttached
-import kotlinx.coroutines.flow.collect
 
-/** Binder for the small clock view, large clock view and smartspace. */
-object KeyguardPreviewClockSmartspaceViewBinder {
+/** Binder for the small clock view, large clock view. */
+object KeyguardPreviewClockViewBinder {
 
     @JvmStatic
     fun bind(
         largeClockHostView: View,
         smallClockHostView: View,
-        smartspace: View?,
-        viewModel: KeyguardPreviewClockSmartspaceViewModel,
+        viewModel: KeyguardPreviewClockViewModel,
     ) {
         largeClockHostView.repeatWhenAttached {
             repeatOnLifecycle(Lifecycle.State.STARTED) {
@@ -46,15 +44,5 @@
                 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/binder/KeyguardPreviewSmartspaceViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardPreviewSmartspaceViewBinder.kt
new file mode 100644
index 0000000..f5e4c6a
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardPreviewSmartspaceViewBinder.kt
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.isInvisible
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.repeatOnLifecycle
+import com.android.systemui.keyguard.ui.viewmodel.KeyguardPreviewSmartspaceViewModel
+import com.android.systemui.lifecycle.repeatWhenAttached
+import kotlinx.coroutines.launch
+
+/** Binder for the small clock view, large clock view and smartspace. */
+object KeyguardPreviewSmartspaceViewBinder {
+
+    @JvmStatic
+    fun bind(
+        smartspace: View,
+        viewModel: KeyguardPreviewSmartspaceViewModel,
+    ) {
+        smartspace.repeatWhenAttached {
+            repeatOnLifecycle(Lifecycle.State.STARTED) {
+                launch { viewModel.smartspaceTopPadding.collect { smartspace.setTopPadding(it) } }
+
+                launch { viewModel.shouldHideSmartspace.collect { smartspace.isInvisible = it } }
+            }
+        }
+    }
+
+    private fun View.setTopPadding(padding: Int) {
+        setPaddingRelative(paddingStart, padding, paddingEnd, paddingBottom)
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardQuickAffordanceOnTouchListener.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardQuickAffordanceOnTouchListener.kt
index 5745d6a..7685345 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardQuickAffordanceOnTouchListener.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardQuickAffordanceOnTouchListener.kt
@@ -46,7 +46,7 @@
     @SuppressLint("ClickableViewAccessibility")
     override fun onTouch(v: View, event: MotionEvent): Boolean {
         return when (event.actionMasked) {
-            MotionEvent.ACTION_DOWN ->
+            MotionEvent.ACTION_DOWN -> {
                 if (viewModel.configKey != null) {
                     downDisplayCoords.set(event.rawX, event.rawY)
                     if (isUsingAccurateTool(event)) {
@@ -62,21 +62,10 @@
                                 .scaleX(PRESSED_SCALE)
                                 .scaleY(PRESSED_SCALE)
                                 .setDuration(longPressDurationMs)
-                                .withEndAction {
-                                    if (
-                                        falsingManager?.isFalseLongTap(
-                                            FalsingManager.MODERATE_PENALTY
-                                        ) == false
-                                    ) {
-                                        dispatchClick(viewModel.configKey)
-                                    }
-                                    cancel()
-                                }
                     }
-                    true
-                } else {
-                    false
                 }
+                false
+            }
             MotionEvent.ACTION_MOVE -> {
                 if (!isUsingAccurateTool(event)) {
                     // Moving too far while performing a long-press gesture cancels that
@@ -91,7 +80,7 @@
                         cancel()
                     }
                 }
-                true
+                false
             }
             MotionEvent.ACTION_UP -> {
                 if (isUsingAccurateTool(event)) {
@@ -146,7 +135,7 @@
                             }
                     )
                 }
-                true
+                false
             }
             MotionEvent.ACTION_CANCEL -> {
                 cancel()
@@ -179,7 +168,7 @@
         view.setOnClickListener(null)
     }
 
-    private fun cancel(onAnimationEnd: Runnable? = null) {
+    fun cancel(onAnimationEnd: Runnable? = null) {
         longPressAnimator?.cancel()
         longPressAnimator = null
         view.animate().scaleX(1f).scaleY(1f).withEndAction(onAnimationEnd)
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 4308d84..fe62bf4 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
@@ -42,9 +42,11 @@
 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.binder.KeyguardPreviewClockViewBinder
+import com.android.systemui.keyguard.ui.binder.KeyguardPreviewSmartspaceViewBinder
 import com.android.systemui.keyguard.ui.viewmodel.KeyguardBottomAreaViewModel
-import com.android.systemui.keyguard.ui.viewmodel.KeyguardPreviewClockSmartspaceViewModel
+import com.android.systemui.keyguard.ui.viewmodel.KeyguardPreviewClockViewModel
+import com.android.systemui.keyguard.ui.viewmodel.KeyguardPreviewSmartspaceViewModel
 import com.android.systemui.plugins.ClockController
 import com.android.systemui.shared.clocks.ClockRegistry
 import com.android.systemui.shared.clocks.DefaultClockController
@@ -65,7 +67,8 @@
     @Application private val context: Context,
     @Main private val mainDispatcher: CoroutineDispatcher,
     @Main private val mainHandler: Handler,
-    private val clockSmartspaceViewModel: KeyguardPreviewClockSmartspaceViewModel,
+    private val clockViewModel: KeyguardPreviewClockViewModel,
+    private val smartspaceViewModel: KeyguardPreviewSmartspaceViewModel,
     private val bottomAreaViewModel: KeyguardBottomAreaViewModel,
     displayManager: DisplayManager,
     private val windowManager: WindowManager,
@@ -129,16 +132,18 @@
             setUpBottomArea(rootView)
 
             setUpSmartspace(rootView)
+            smartSpaceView?.let {
+                KeyguardPreviewSmartspaceViewBinder.bind(it, smartspaceViewModel)
+            }
 
             setUpUdfps(rootView)
 
             if (!shouldHideClock) {
                 setUpClock(rootView)
-                KeyguardPreviewClockSmartspaceViewBinder.bind(
+                KeyguardPreviewClockViewBinder.bind(
                     largeClockHostView,
                     smallClockHostView,
-                    smartSpaceView,
-                    clockSmartspaceViewModel,
+                    clockViewModel,
                 )
             }
 
@@ -219,8 +224,8 @@
         smartSpaceView = lockscreenSmartspaceController.buildAndConnectDateView(parentView)
 
         val topPadding: Int =
-            KeyguardPreviewClockSmartspaceViewModel.getLargeClockSmartspaceTopPadding(
-                context.resources
+            KeyguardPreviewSmartspaceViewModel.getLargeClockSmartspaceTopPadding(
+                context.resources,
             )
 
         val startPadding: Int =
@@ -235,7 +240,7 @@
         smartSpaceView?.let {
             it.setPaddingRelative(startPadding, topPadding, endPadding, 0)
             it.isClickable = false
-
+            it.isInvisible = true
             parentView.addView(
                 it,
                 FrameLayout.LayoutParams(
@@ -372,7 +377,7 @@
                 resources.getDimensionPixelSize(R.dimen.small_clock_height)
             )
         layoutParams.topMargin =
-            KeyguardPreviewClockSmartspaceViewModel.getStatusBarHeight(resources) +
+            KeyguardPreviewSmartspaceViewModel.getStatusBarHeight(resources) +
                 resources.getDimensionPixelSize(R.dimen.small_clock_padding_top)
         hostView.layoutParams = layoutParams
 
@@ -394,9 +399,6 @@
 
         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) {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardPreviewClockViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardPreviewClockViewModel.kt
new file mode 100644
index 0000000..5301302
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardPreviewClockViewModel.kt
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.systemui.keyguard.ui.viewmodel
+
+import android.content.Context
+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. */
+class KeyguardPreviewClockViewModel
+@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 }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardPreviewClockSmartspaceViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardPreviewSmartspaceViewModel.kt
similarity index 74%
rename from packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardPreviewClockSmartspaceViewModel.kt
rename to packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardPreviewSmartspaceViewModel.kt
index 00c603b..bf51976 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardPreviewClockSmartspaceViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardPreviewSmartspaceViewModel.kt
@@ -24,23 +24,18 @@
 import com.android.systemui.keyguard.shared.model.SettingsClockSize
 import javax.inject.Inject
 import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.combine
 import kotlinx.coroutines.flow.map
 
-/** View model for the small clock view, large clock view and smartspace. */
-class KeyguardPreviewClockSmartspaceViewModel
+/** View model for the smartspace. */
+class KeyguardPreviewSmartspaceViewModel
 @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> =
+    val smartspaceTopPadding: Flow<Int> =
         interactor.selectedClockSize.map {
             when (it) {
                 SettingsClockSize.DYNAMIC -> getLargeClockSmartspaceTopPadding(context.resources)
@@ -48,6 +43,22 @@
             }
         }
 
+    val shouldHideSmartspace: Flow<Boolean> =
+        combine(
+                interactor.selectedClockSize,
+                interactor.currentClockId,
+                ::Pair,
+            )
+            .map { (size, currentClockId) ->
+                when (size) {
+                    // TODO (b/284122375) This is temporary. We should use clockController
+                    //      .largeClock.config.hasCustomWeatherDataDisplay instead, but
+                    //      ClockRegistry.createCurrentClock is not reliable.
+                    SettingsClockSize.DYNAMIC -> currentClockId == "DIGITAL_CLOCK_WEATHER"
+                    SettingsClockSize.SMALL -> false
+                }
+            }
+
     companion object {
         fun getLargeClockSmartspaceTopPadding(resources: Resources): Int {
             return with(resources) {
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaProjectionPermissionActivity.java b/packages/SystemUI/src/com/android/systemui/media/MediaProjectionPermissionActivity.java
index c9c2ea2..f6a2f37 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaProjectionPermissionActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaProjectionPermissionActivity.java
@@ -29,6 +29,7 @@
 import android.app.Activity;
 import android.app.ActivityManager;
 import android.app.AlertDialog;
+import android.content.Context;
 import android.content.DialogInterface;
 import android.content.Intent;
 import android.content.pm.ApplicationInfo;
@@ -203,14 +204,17 @@
             dialogTitle = getString(R.string.media_projection_dialog_title, appName);
         }
 
+        // Using application context for the dialog, instead of the activity context, so we get
+        // the correct screen width when in split screen.
+        Context dialogContext = getApplicationContext();
         if (isPartialScreenSharingEnabled()) {
-            mDialog = new MediaProjectionPermissionDialog(this, () -> {
+            mDialog = new MediaProjectionPermissionDialog(dialogContext, () -> {
                 ScreenShareOption selectedOption =
                         ((MediaProjectionPermissionDialog) mDialog).getSelectedScreenShareOption();
                 grantMediaProjectionPermission(selectedOption.getMode());
             }, () -> finish(RECORD_CANCEL, /* projection= */ null), appName);
         } else {
-            AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(this,
+            AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(dialogContext,
                     R.style.Theme_SystemUI_Dialog)
                     .setTitle(dialogTitle)
                     .setIcon(R.drawable.ic_media_projection_permission)
@@ -263,7 +267,10 @@
         final UserHandle hostUserHandle = getHostUserHandle();
         if (mScreenCaptureDevicePolicyResolver.get()
                 .isScreenCaptureCompletelyDisabled(hostUserHandle)) {
-            AlertDialog dialog = new ScreenCaptureDisabledDialog(this);
+            // Using application context for the dialog, instead of the activity context, so we get
+            // the correct screen width when in split screen.
+            Context dialogContext = getApplicationContext();
+            AlertDialog dialog = new ScreenCaptureDisabledDialog(dialogContext);
             setUpDialog(dialog);
             dialog.show();
             return true;
diff --git a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskRoleManagerExt.kt b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskRoleManagerExt.kt
index 441b9f5..a2ebead 100644
--- a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskRoleManagerExt.kt
+++ b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskRoleManagerExt.kt
@@ -19,6 +19,7 @@
 import android.app.role.RoleManager
 import android.app.role.RoleManager.ROLE_NOTES
 import android.content.Context
+import android.content.pm.PackageManager
 import android.content.pm.ShortcutInfo
 import android.graphics.drawable.Icon
 import android.os.PersistableBundle
@@ -42,20 +43,41 @@
         context: Context,
         user: UserHandle,
     ): ShortcutInfo {
+        val packageName = getDefaultRoleHolderAsUser(ROLE_NOTES, user)
+
         val extras = PersistableBundle()
-        getDefaultRoleHolderAsUser(ROLE_NOTES, user)?.let { packageName ->
+        if (packageName != null) {
             // Set custom app badge using the icon from ROLES_NOTES default app.
             extras.putString(NoteTaskController.EXTRA_SHORTCUT_BADGE_OVERRIDE_PACKAGE, packageName)
         }
 
+        val shortLabel = context.getString(R.string.note_task_button_label)
+
+        val applicationLabel = context.packageManager.getApplicationLabel(packageName)
+        val longLabel =
+            if (applicationLabel == null) {
+                shortLabel
+            } else {
+                context.getString(
+                    R.string.note_task_shortcut_long_label,
+                    applicationLabel,
+                )
+            }
+
         val icon = Icon.createWithResource(context, R.drawable.ic_note_task_shortcut_widget)
 
         return ShortcutInfo.Builder(context, NoteTaskController.SHORTCUT_ID)
             .setIntent(LaunchNoteTaskActivity.newIntent(context = context))
-            .setShortLabel(context.getString(R.string.note_task_button_label))
+            .setShortLabel(shortLabel)
+            .setLongLabel(longLabel)
             .setLongLived(true)
             .setIcon(icon)
             .setExtras(extras)
             .build()
     }
+
+    private fun PackageManager.getApplicationLabel(packageName: String?): String? =
+        runCatching { getApplicationInfo(packageName, /* flags= */ 0)!! }
+            .getOrNull()
+            ?.let { info -> getApplicationLabel(info).toString() }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialog.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialog.java
index 380b85c..212a2b7 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialog.java
@@ -286,7 +286,6 @@
         mHandler.removeCallbacks(mHideProgressBarRunnable);
         mHandler.removeCallbacks(mHideSearchingRunnable);
         mMobileNetworkLayout.setOnClickListener(null);
-        mMobileDataToggle.setOnCheckedChangeListener(null);
         mConnectedWifListLayout.setOnClickListener(null);
         if (mSecondaryMobileNetworkLayout != null) {
             mSecondaryMobileNetworkLayout.setOnClickListener(null);
@@ -351,18 +350,16 @@
             }
             mInternetDialogController.connectCarrierNetwork();
         });
-        mMobileDataToggle.setOnCheckedChangeListener(
-                (buttonView, isChecked) -> {
-                    if (!isChecked && shouldShowMobileDialog()) {
-                        showTurnOffMobileDialog();
-                    } else if (!shouldShowMobileDialog()) {
-                        if (mInternetDialogController.isMobileDataEnabled() == isChecked) {
-                            return;
-                        }
-                        mInternetDialogController.setMobileDataEnabled(mContext, mDefaultDataSubId,
-                                isChecked, false);
-                    }
-                });
+        mMobileDataToggle.setOnClickListener(v -> {
+            boolean isChecked = mMobileDataToggle.isChecked();
+            if (!isChecked && shouldShowMobileDialog()) {
+                mMobileDataToggle.setChecked(true);
+                showTurnOffMobileDialog();
+            } else if (mInternetDialogController.isMobileDataEnabled() != isChecked) {
+                mInternetDialogController.setMobileDataEnabled(mContext, mDefaultDataSubId,
+                        isChecked, false);
+            }
+        });
         mConnectedWifListLayout.setOnClickListener(this::onClickConnectedWifi);
         mSeeAllLayout.setOnClickListener(this::onClickSeeMoreButton);
         mWiFiToggle.setOnCheckedChangeListener(
@@ -696,9 +693,7 @@
         mAlertDialog = new Builder(mContext)
                 .setTitle(R.string.mobile_data_disable_title)
                 .setMessage(mContext.getString(R.string.mobile_data_disable_message, carrierName))
-                .setNegativeButton(android.R.string.cancel, (d, w) -> {
-                    mMobileDataToggle.setChecked(true);
-                })
+                .setNegativeButton(android.R.string.cancel, (d, w) -> {})
                 .setPositiveButton(
                         com.android.internal.R.string.alert_windows_notification_turn_off_action,
                         (d, w) -> {
@@ -708,7 +703,6 @@
                             Prefs.putBoolean(mContext, QS_HAS_TURNED_OFF_MOBILE_DATA, true);
                         })
                 .create();
-        mAlertDialog.setOnCancelListener(dialog -> mMobileDataToggle.setChecked(true));
         mAlertDialog.getWindow().setType(WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG);
         SystemUIDialog.setShowForAllUsers(mAlertDialog, true);
         SystemUIDialog.registerDismissListener(mAlertDialog);
diff --git a/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialog.kt b/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialog.kt
index bfaf3d0..604d449 100644
--- a/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialog.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialog.kt
@@ -93,6 +93,7 @@
             }
             dismiss()
         }
+        setCancelButtonOnClickListener { dismiss() }
         initRecordOptionsView()
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/scrim/ScrimDrawable.java b/packages/SystemUI/src/com/android/systemui/scrim/ScrimDrawable.java
index 10e2afe..9a1ffcb 100644
--- a/packages/SystemUI/src/com/android/systemui/scrim/ScrimDrawable.java
+++ b/packages/SystemUI/src/com/android/systemui/scrim/ScrimDrawable.java
@@ -203,7 +203,10 @@
     }
 
     public void setBottomEdgeRadius(float radius) {
-        mBottomEdgeRadius = radius;
+        if (mBottomEdgeRadius != radius) {
+            mBottomEdgeRadius = radius;
+            invalidateSelf();
+        }
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
index 784a360..047fea1 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
@@ -2206,6 +2206,7 @@
         // TODO: non-linearly transform progress fraction into squish amount (ease-in, linear out)
         mCurrentBackProgress = progressFraction;
         applyBackScaling(progressFraction);
+        mQsController.setClippingBounds();
     }
 
     /** Resets back progress. */
diff --git a/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java b/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java
index 8672260..c42c2f4 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java
@@ -114,6 +114,8 @@
 public class QuickSettingsController implements Dumpable {
     public static final String TAG = "QuickSettingsController";
 
+    public static final int SHADE_BACK_ANIM_SCALE_MULTIPLIER = 100;
+
     private QS mQs;
     private final Lazy<NotificationPanelViewController> mPanelViewControllerLazy;
 
@@ -1128,7 +1130,7 @@
      * Updates scrim bounds, QS clipping, notifications clipping and keyguard status view clipping
      * as well based on the bounds of the shade and QS state.
      */
-    private void setClippingBounds() {
+    void setClippingBounds() {
         float qsExpansionFraction = computeExpansionFraction();
         final int qsPanelBottomY = calculateBottomPosition(qsExpansionFraction);
         // Split shade has no QQS
@@ -1216,7 +1218,10 @@
                             ? 0 : mScreenCornerRadius;
             radius = (int) MathUtils.lerp(screenCornerRadius, mScrimCornerRadius,
                     Math.min(top / (float) mScrimCornerRadius, 1f));
-            mScrimController.setNotificationBottomRadius(radius);
+
+            float bottomRadius = mExpanded ? screenCornerRadius :
+                    calculateBottomCornerRadius(screenCornerRadius);
+            mScrimController.setNotificationBottomRadius(bottomRadius);
         }
         if (isQsFragmentCreated()) {
             float qsTranslation = 0;
@@ -1279,6 +1284,28 @@
                 nsslLeft, nsslTop, nsslRight, nsslBottom, topRadius, bottomRadius);
     }
 
+    /**
+     * Bottom corner radius should follow screen corner radius unless
+     * predictive back is running. We want a smooth transition from screen
+     * corner radius to scrim corner radius as the notification scrim is scaled down,
+     * but the transition should be brief enough to accommodate very short back gestures.
+     */
+    @VisibleForTesting
+    int calculateBottomCornerRadius(float screenCornerRadius) {
+        return (int) MathUtils.lerp(screenCornerRadius, mScrimCornerRadius,
+                Math.min(calculateBottomRadiusProgress(), 1f));
+    }
+
+    @VisibleForTesting
+    float calculateBottomRadiusProgress() {
+        return (1 - mScrimController.getBackScaling()) * SHADE_BACK_ANIM_SCALE_MULTIPLIER;
+    }
+
+    @VisibleForTesting
+    int getScrimCornerRadius() {
+        return mScrimCornerRadius;
+    }
+
     void setDisplayInsets(int leftInset, int rightInset) {
         mDisplayLeftInset = leftInset;
         mDisplayRightInset = rightInset;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
index 53c40b8..cab95c6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
@@ -500,7 +500,6 @@
     private final AlternateBouncerInteractor mAlternateBouncerInteractor;
 
     private final PluginDependencyProvider mPluginDependencyProvider;
-    private final KeyguardDismissUtil mKeyguardDismissUtil;
     private final ExtensionController mExtensionController;
     private final UserInfoControllerImpl mUserInfoControllerImpl;
     private final DemoModeController mDemoModeController;
@@ -787,7 +786,6 @@
             InitController initController,
             @Named(TIME_TICK_HANDLER_NAME) Handler timeTickHandler,
             PluginDependencyProvider pluginDependencyProvider,
-            KeyguardDismissUtil keyguardDismissUtil,
             ExtensionController extensionController,
             UserInfoControllerImpl userInfoControllerImpl,
             PhoneStatusBarPolicy phoneStatusBarPolicy,
@@ -884,7 +882,6 @@
         mKeyguardViewMediatorCallback = viewMediatorCallback;
         mInitController = initController;
         mPluginDependencyProvider = pluginDependencyProvider;
-        mKeyguardDismissUtil = keyguardDismissUtil;
         mExtensionController = extensionController;
         mUserInfoControllerImpl = userInfoControllerImpl;
         mIconPolicy = phoneStatusBarPolicy;
@@ -1742,7 +1739,6 @@
 
         mLightBarController.setBiometricUnlockController(mBiometricUnlockController);
         mMediaManager.setBiometricUnlockController(mBiometricUnlockController);
-        mKeyguardDismissUtil.setDismissHandler(this::executeWhenUnlocked);
         Trace.endSection();
     }
 
@@ -2360,15 +2356,6 @@
         mNotificationsController.resetUserExpandedStates();
     }
 
-    private void executeWhenUnlocked(OnDismissAction action, boolean requiresShadeOpen,
-            boolean afterKeyguardGone) {
-        if (mKeyguardStateController.isShowing() && requiresShadeOpen) {
-            mStatusBarStateController.setLeaveOpenOnKeyguardHide(true);
-        }
-        mActivityStarter.dismissKeyguardThenExecute(action, null /* cancelAction */,
-                afterKeyguardGone /* afterKeyguardGone */);
-    }
-
     /**
      * Notify the shade controller that the current user changed
      *
@@ -2838,7 +2825,11 @@
     }
 
     private void updateDozingState() {
-        Trace.traceCounter(Trace.TRACE_TAG_APP, "dozing", mDozing ? 1 : 0);
+        if (Trace.isTagEnabled(Trace.TRACE_TAG_APP)) {
+            Trace.asyncTraceForTrackEnd(Trace.TRACE_TAG_APP, "Dozing", 0);
+            Trace.asyncTraceForTrackBegin(Trace.TRACE_TAG_APP, "Dozing", String.valueOf(mDozing),
+                    0);
+        }
         Trace.beginSection("CentralSurfaces#updateDozingState");
 
         boolean keyguardVisible = mKeyguardStateController.isVisible();
@@ -3187,6 +3178,10 @@
 
         @Override
         public void onStartedWakingUp() {
+            // Between onStartedWakingUp() and onFinishedWakingUp(), the system is changing the
+            // display power mode. To avoid jank, animations should NOT run during these power
+            // mode transitions, which means that whenever possible, animations should
+            // start running during the onFinishedWakingUp() callback instead of this callback.
             String tag = "CentralSurfaces#onStartedWakingUp";
             DejankUtils.startDetectingBlockingIpcs(tag);
             mNotificationShadeWindowController.batchApplyWindowLayoutParams(()-> {
@@ -3231,6 +3226,14 @@
 
                 updateVisibleToUser();
                 updateIsKeyguard();
+            });
+            DejankUtils.stopDetectingBlockingIpcs(tag);
+        }
+
+        @Override
+        public void onFinishedWakingUp() {
+            mNotificationShadeWindowController.batchApplyWindowLayoutParams(()-> {
+                // stopDozing() starts the LOCKSCREEN_TRANSITION_FROM_AOD animation.
                 mDozeServiceHost.stopDozing();
                 // This is intentionally below the stopDozing call above, since it avoids that we're
                 // unnecessarily animating the wakeUp transition. Animations should only be enabled
@@ -3244,13 +3247,7 @@
                 if (mScreenOffAnimationController.shouldHideLightRevealScrimOnWakeUp()) {
                     mShadeController.makeExpandedInvisible();
                 }
-
             });
-            DejankUtils.stopDetectingBlockingIpcs(tag);
-        }
-
-        @Override
-        public void onFinishedWakingUp() {
             mWakeUpCoordinator.setFullyAwake(true);
             mWakeUpCoordinator.setWakingUp(false, false);
             if (mKeyguardStateController.isOccluded()
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardDismissUtil.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardDismissUtil.java
index 27b68f2..1c90c0d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardDismissUtil.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardDismissUtil.java
@@ -16,48 +16,41 @@
 
 package com.android.systemui.statusbar.phone;
 
-import android.util.Log;
-
 import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.plugins.ActivityStarter;
 import com.android.systemui.plugins.ActivityStarter.OnDismissAction;
+import com.android.systemui.statusbar.SysuiStatusBarStateController;
+import com.android.systemui.statusbar.policy.KeyguardStateController;
 
 import javax.inject.Inject;
 
 /**
- * Executes actions that require the screen to be unlocked. Delegates the actual handling to an
- * implementation passed via {@link #setDismissHandler}.
+ * Executes actions that require the screen to be unlocked.
  */
 @SysUISingleton
 public class KeyguardDismissUtil implements KeyguardDismissHandler {
-    private static final String TAG = "KeyguardDismissUtil";
+    private final KeyguardStateController mKeyguardStateController;
 
-    private volatile KeyguardDismissHandler mDismissHandler;
+    private final SysuiStatusBarStateController mStatusBarStateController;
+
+    private final ActivityStarter mActivityStarter;
 
     @Inject
-    public KeyguardDismissUtil() {
+    public KeyguardDismissUtil(KeyguardStateController keyguardStateController,
+            SysuiStatusBarStateController statusBarStateController,
+            ActivityStarter activityStarter) {
+        mKeyguardStateController = keyguardStateController;
+        mStatusBarStateController = statusBarStateController;
+        mActivityStarter = activityStarter;
     }
 
-    /** Sets the actual {@link KeyguardDismissHandler} implementation. */
-    public void setDismissHandler(KeyguardDismissHandler dismissHandler) {
-        mDismissHandler = dismissHandler;
-    }
-
-    /**
-     * Executes an action that requires the screen to be unlocked.
-     *
-     * <p>Must be called after {@link #setDismissHandler}.
-     *
-     * @param requiresShadeOpen does the shade need to be forced open when hiding the keyguard?
-     */
     @Override
     public void executeWhenUnlocked(OnDismissAction action, boolean requiresShadeOpen,
             boolean afterKeyguardGone) {
-        KeyguardDismissHandler dismissHandler = mDismissHandler;
-        if (dismissHandler == null) {
-            Log.wtf(TAG, "KeyguardDismissHandler not set.");
-            action.onDismiss();
-            return;
+        if (mKeyguardStateController.isShowing() && requiresShadeOpen) {
+            mStatusBarStateController.setLeaveOpenOnKeyguardHide(true);
         }
-        dismissHandler.executeWhenUnlocked(action, requiresShadeOpen, afterKeyguardGone);
+        mActivityStarter.dismissKeyguardThenExecute(action, null /* cancelAction */,
+                afterKeyguardGone /* afterKeyguardGone */);
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
index fdb772b..25ecf1a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
@@ -602,6 +602,10 @@
         mNotificationsScrim.setScaleY(scale);
     }
 
+    public float getBackScaling() {
+        return mNotificationsScrim.getScaleY();
+    }
+
     public void onTrackingStarted() {
         mDarkenWhileDragging = !mKeyguardStateController.canDismissLockScreen();
         if (!mKeyguardUnlockAnimationController.isPlayingCannedUnlockAnimation()) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
index 7d0e3b3..a34cc96 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
@@ -495,6 +495,7 @@
 
         return mKeyguardStateController.isShowing()
                 && !primaryBouncerIsOrWillBeShowing()
+                && !mKeyguardStateController.isKeyguardGoingAway()
                 && isUserTrackingStarted
                 && !hideBouncerOverDream
                 && !mKeyguardStateController.isOccluded()
diff --git a/packages/SystemUI/src/com/android/systemui/util/ActivityTaskManagerProxy.kt b/packages/SystemUI/src/com/android/systemui/util/ActivityTaskManagerProxy.kt
new file mode 100644
index 0000000..6e82cf6
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/util/ActivityTaskManagerProxy.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.util
+
+import android.app.ActivityTaskManager
+import android.content.Context
+import com.android.systemui.dagger.SysUISingleton
+import javax.inject.Inject
+
+/** Proxy for static calls to [ActivityTaskManager]. */
+@SysUISingleton
+class ActivityTaskManagerProxy @Inject constructor() {
+
+    /** Calls [ActivityTaskManager.supportsMultiWindow] */
+    fun supportsMultiWindow(context: Context) = ActivityTaskManager.supportsMultiWindow(context)
+}
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 9f5c181..374c28d6 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
@@ -82,7 +82,7 @@
             assertThat(message).isEqualTo(MESSAGE_ENTER_YOUR_PIN)
 
             // Wrong input.
-            underTest.authenticate(listOf(9, 8, 7))
+            assertThat(underTest.authenticate(listOf(9, 8, 7))).isFalse()
             assertThat(message).isEqualTo(MESSAGE_WRONG_PIN)
             assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
 
@@ -90,7 +90,7 @@
             assertThat(message).isEqualTo(MESSAGE_ENTER_YOUR_PIN)
 
             // Correct input.
-            underTest.authenticate(listOf(1, 2, 3, 4))
+            assertThat(underTest.authenticate(listOf(1, 2, 3, 4))).isTrue()
             assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Gone))
         }
 
@@ -114,7 +114,7 @@
             assertThat(message).isEqualTo(MESSAGE_ENTER_YOUR_PASSWORD)
 
             // Wrong input.
-            underTest.authenticate("alohamora".toList())
+            assertThat(underTest.authenticate("alohamora".toList())).isFalse()
             assertThat(message).isEqualTo(MESSAGE_WRONG_PASSWORD)
             assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
 
@@ -122,7 +122,7 @@
             assertThat(message).isEqualTo(MESSAGE_ENTER_YOUR_PASSWORD)
 
             // Correct input.
-            underTest.authenticate("password".toList())
+            assertThat(underTest.authenticate("password".toList())).isTrue()
             assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Gone))
         }
 
@@ -146,9 +146,12 @@
             assertThat(message).isEqualTo(MESSAGE_ENTER_YOUR_PATTERN)
 
             // Wrong input.
-            underTest.authenticate(
-                listOf(AuthenticationMethodModel.Pattern.PatternCoordinate(3, 4))
-            )
+            assertThat(
+                    underTest.authenticate(
+                        listOf(AuthenticationMethodModel.Pattern.PatternCoordinate(3, 4))
+                    )
+                )
+                .isFalse()
             assertThat(message).isEqualTo(MESSAGE_WRONG_PATTERN)
             assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
 
@@ -156,7 +159,7 @@
             assertThat(message).isEqualTo(MESSAGE_ENTER_YOUR_PATTERN)
 
             // Correct input.
-            underTest.authenticate(emptyList())
+            assertThat(underTest.authenticate(emptyList())).isTrue()
             assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Gone))
         }
 
@@ -214,7 +217,7 @@
             assertThat(isUnlocked).isFalse()
             repeat(BouncerInteractor.THROTTLE_EVERY) { times ->
                 // Wrong PIN.
-                underTest.authenticate(listOf(6, 7, 8, 9))
+                assertThat(underTest.authenticate(listOf(6, 7, 8, 9))).isFalse()
                 if (times < BouncerInteractor.THROTTLE_EVERY - 1) {
                     assertThat(message).isEqualTo(MESSAGE_WRONG_PIN)
                 }
@@ -223,7 +226,7 @@
             assertTryAgainMessage(message, BouncerInteractor.THROTTLE_DURATION_SEC)
 
             // Correct PIN, but throttled, so doesn't unlock:
-            underTest.authenticate(listOf(1, 2, 3, 4))
+            assertThat(underTest.authenticate(listOf(1, 2, 3, 4))).isFalse()
             assertThat(isUnlocked).isFalse()
             assertTryAgainMessage(message, BouncerInteractor.THROTTLE_DURATION_SEC)
 
@@ -241,7 +244,7 @@
             assertThat(isUnlocked).isFalse()
 
             // Correct PIN and no longer throttled so unlocks:
-            underTest.authenticate(listOf(1, 2, 3, 4))
+            assertThat(underTest.authenticate(listOf(1, 2, 3, 4))).isTrue()
             assertThat(isUnlocked).isTrue()
         }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModelTest.kt
new file mode 100644
index 0000000..1642410
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModelTest.kt
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.bouncer.ui.viewmodel
+
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
+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.MutableStateFlow
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(JUnit4::class)
+class AuthMethodBouncerViewModelTest : SysuiTestCase() {
+
+    private val testScope = TestScope()
+    private val utils = SceneTestUtils(this, testScope)
+    private val authenticationInteractor =
+        utils.authenticationInteractor(
+            utils.authenticationRepository(),
+        )
+    private val underTest =
+        PinBouncerViewModel(
+            applicationScope = testScope.backgroundScope,
+            interactor =
+                utils.bouncerInteractor(
+                    authenticationInteractor = authenticationInteractor,
+                    sceneInteractor = utils.sceneInteractor(),
+                ),
+            isInputEnabled = MutableStateFlow(true),
+        )
+
+    @Test
+    fun animateFailure() =
+        testScope.runTest {
+            authenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.PIN(1234))
+            val animateFailure by collectLastValue(underTest.animateFailure)
+            assertThat(animateFailure).isFalse()
+
+            // Wrong PIN:
+            underTest.onPinButtonClicked(3)
+            underTest.onPinButtonClicked(4)
+            underTest.onPinButtonClicked(5)
+            underTest.onPinButtonClicked(6)
+            underTest.onAuthenticateButtonClicked()
+            assertThat(animateFailure).isTrue()
+
+            underTest.onFailureAnimationShown()
+            assertThat(animateFailure).isFalse()
+
+            // Correct PIN:
+            underTest.onPinButtonClicked(1)
+            underTest.onPinButtonClicked(2)
+            underTest.onPinButtonClicked(3)
+            underTest.onPinButtonClicked(4)
+            underTest.onAuthenticateButtonClicked()
+            assertThat(animateFailure).isFalse()
+        }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelTest.kt
index b942ccb..e8c946c 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
@@ -93,22 +93,21 @@
     }
 
     @Test
-    fun isMessageUpdateAnimationsEnabled() =
+    fun message() =
         testScope.runTest {
-            val isMessageUpdateAnimationsEnabled by
-                collectLastValue(underTest.isMessageUpdateAnimationsEnabled)
+            val message by collectLastValue(underTest.message)
             val throttling by collectLastValue(bouncerInteractor.throttling)
             authenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.PIN(1234))
-            assertThat(isMessageUpdateAnimationsEnabled).isTrue()
+            assertThat(message?.isUpdateAnimated).isTrue()
 
             repeat(BouncerInteractor.THROTTLE_EVERY) {
                 // Wrong PIN.
                 bouncerInteractor.authenticate(listOf(3, 4, 5, 6))
             }
-            assertThat(isMessageUpdateAnimationsEnabled).isFalse()
+            assertThat(message?.isUpdateAnimated).isFalse()
 
             throttling?.totalDurationSec?.let { seconds -> advanceTimeBy(seconds * 1000L) }
-            assertThat(isMessageUpdateAnimationsEnabled).isTrue()
+            assertThat(message?.isUpdateAnimated).isTrue()
         }
 
     @Test
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 b7b90de..f436aa3 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
@@ -85,7 +85,7 @@
 
             underTest.onShown()
 
-            assertThat(message).isEqualTo(ENTER_YOUR_PASSWORD)
+            assertThat(message?.text).isEqualTo(ENTER_YOUR_PASSWORD)
             assertThat(password).isEqualTo("")
             assertThat(isUnlocked).isFalse()
             assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
@@ -109,7 +109,7 @@
 
             underTest.onPasswordInputChanged("password")
 
-            assertThat(message).isEmpty()
+            assertThat(message?.text).isEmpty()
             assertThat(password).isEqualTo("password")
             assertThat(isUnlocked).isFalse()
             assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
@@ -156,7 +156,7 @@
             underTest.onAuthenticateKeyPressed()
 
             assertThat(password).isEqualTo("")
-            assertThat(message).isEqualTo(WRONG_PASSWORD)
+            assertThat(message?.text).isEqualTo(WRONG_PASSWORD)
             assertThat(isUnlocked).isFalse()
             assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
         }
@@ -179,13 +179,13 @@
             underTest.onPasswordInputChanged("wrong")
             underTest.onAuthenticateKeyPressed()
             assertThat(password).isEqualTo("")
-            assertThat(message).isEqualTo(WRONG_PASSWORD)
+            assertThat(message?.text).isEqualTo(WRONG_PASSWORD)
             assertThat(isUnlocked).isFalse()
             assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
 
             // Enter the correct password:
             underTest.onPasswordInputChanged("password")
-            assertThat(message).isEmpty()
+            assertThat(message?.text).isEmpty()
 
             underTest.onAuthenticateKeyPressed()
 
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 b588ba2..d7d7154 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
@@ -89,7 +89,7 @@
 
             underTest.onShown()
 
-            assertThat(message).isEqualTo(ENTER_YOUR_PATTERN)
+            assertThat(message?.text).isEqualTo(ENTER_YOUR_PATTERN)
             assertThat(selectedDots).isEmpty()
             assertThat(currentDot).isNull()
             assertThat(isUnlocked).isFalse()
@@ -115,7 +115,7 @@
 
             underTest.onDragStart()
 
-            assertThat(message).isEmpty()
+            assertThat(message?.text).isEmpty()
             assertThat(selectedDots).isEmpty()
             assertThat(currentDot).isNull()
             assertThat(isUnlocked).isFalse()
@@ -202,7 +202,7 @@
 
             assertThat(selectedDots).isEmpty()
             assertThat(currentDot).isNull()
-            assertThat(message).isEqualTo(WRONG_PATTERN)
+            assertThat(message?.text).isEqualTo(WRONG_PATTERN)
             assertThat(isUnlocked).isFalse()
             assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
         }
@@ -235,7 +235,7 @@
             underTest.onDragEnd()
             assertThat(selectedDots).isEmpty()
             assertThat(currentDot).isNull()
-            assertThat(message).isEqualTo(WRONG_PATTERN)
+            assertThat(message?.text).isEqualTo(WRONG_PATTERN)
             assertThat(isUnlocked).isFalse()
             assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
 
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 83f9687..3bdaf05 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
@@ -94,7 +94,7 @@
 
             underTest.onShown()
 
-            assertThat(message).isEqualTo(ENTER_YOUR_PIN)
+            assertThat(message?.text).isEqualTo(ENTER_YOUR_PIN)
             assertThat(pinLengths).isEqualTo(0 to 0)
             assertThat(isUnlocked).isFalse()
             assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
@@ -116,7 +116,7 @@
 
             underTest.onPinButtonClicked(1)
 
-            assertThat(message).isEmpty()
+            assertThat(message?.text).isEmpty()
             assertThat(pinLengths).isEqualTo(0 to 1)
             assertThat(isUnlocked).isFalse()
             assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
@@ -140,7 +140,7 @@
 
             underTest.onBackspaceButtonClicked()
 
-            assertThat(message).isEmpty()
+            assertThat(message?.text).isEmpty()
             assertThat(pinLengths).isEqualTo(1 to 0)
             assertThat(isUnlocked).isFalse()
             assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
@@ -170,7 +170,7 @@
                 advanceTimeBy(PinBouncerViewModel.BACKSPACE_LONG_PRESS_DELAY_MS)
             }
 
-            assertThat(message).isEmpty()
+            assertThat(message?.text).isEmpty()
             assertThat(pinLengths).isEqualTo(1 to 0)
             assertThat(isUnlocked).isFalse()
             assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
@@ -220,7 +220,7 @@
             underTest.onAuthenticateButtonClicked()
 
             assertThat(pinLengths).isEqualTo(0 to 0)
-            assertThat(message).isEqualTo(WRONG_PIN)
+            assertThat(message?.text).isEqualTo(WRONG_PIN)
             assertThat(isUnlocked).isFalse()
             assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
         }
@@ -244,7 +244,7 @@
             underTest.onPinButtonClicked(4)
             underTest.onPinButtonClicked(5) // PIN is now wrong!
             underTest.onAuthenticateButtonClicked()
-            assertThat(message).isEqualTo(WRONG_PIN)
+            assertThat(message?.text).isEqualTo(WRONG_PIN)
             assertThat(pinLengths).isEqualTo(0 to 0)
             assertThat(isUnlocked).isFalse()
             assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
@@ -254,7 +254,7 @@
             underTest.onPinButtonClicked(2)
             underTest.onPinButtonClicked(3)
             underTest.onPinButtonClicked(4)
-            assertThat(message).isEmpty()
+            assertThat(message?.text).isEmpty()
 
             underTest.onAuthenticateButtonClicked()
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsListingControllerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsListingControllerImplTest.kt
index 10bfc1b..ee213f7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsListingControllerImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsListingControllerImplTest.kt
@@ -39,6 +39,7 @@
 import com.android.systemui.flags.Flags.APP_PANELS_ALL_APPS_ALLOWED
 import com.android.systemui.flags.Flags.USE_APP_PANELS
 import com.android.systemui.settings.UserTracker
+import com.android.systemui.util.ActivityTaskManagerProxy
 import com.android.systemui.util.concurrency.FakeExecutor
 import com.android.systemui.util.mockito.any
 import com.android.systemui.util.mockito.argThat
@@ -88,6 +89,8 @@
     private lateinit var packageManager: PackageManager
     @Mock
     private lateinit var featureFlags: FeatureFlags
+    @Mock
+    private lateinit var activityTaskManagerProxy: ActivityTaskManagerProxy
 
     private var componentName = ComponentName("pkg", "class1")
     private var activityName = ComponentName("pkg", "activity")
@@ -112,6 +115,7 @@
         // Return disabled by default
         `when`(packageManager.getComponentEnabledSetting(any()))
                 .thenReturn(PackageManager.COMPONENT_ENABLED_STATE_DISABLED)
+        `when`(activityTaskManagerProxy.supportsMultiWindow(any())).thenReturn(true)
         mContext.setMockPackageManager(packageManager)
 
         mContext.orCreateTestableResources
@@ -136,6 +140,7 @@
                 executor,
                 { mockSL },
                 userTracker,
+                activityTaskManagerProxy,
                 dumpManager,
                 featureFlags
         )
@@ -171,6 +176,7 @@
                 exec,
                 { mockServiceListing },
                 userTracker,
+                activityTaskManagerProxy,
                 dumpManager,
                 featureFlags
         )
@@ -637,7 +643,34 @@
         assertThat(services[0].serviceInfo.componentName).isEqualTo(componentName)
     }
 
+    @Test
+    fun testNoPanelIfMultiWindowNotSupported() {
+        `when`(activityTaskManagerProxy.supportsMultiWindow(any())).thenReturn(false)
 
+        val serviceInfo = ServiceInfo(
+            componentName,
+            activityName
+        )
+
+        `when`(packageManager.getComponentEnabledSetting(eq(activityName)))
+            .thenReturn(PackageManager.COMPONENT_ENABLED_STATE_DEFAULT)
+
+        setUpQueryResult(listOf(
+            ActivityInfo(
+                activityName,
+                enabled = true,
+                exported = true,
+                permission = Manifest.permission.BIND_CONTROLS
+            )
+        ))
+
+        val list = listOf(serviceInfo)
+        serviceListingCallbackCaptor.value.onServicesReloaded(list)
+
+        executor.runAllReady()
+
+        assertNull(controller.getCurrentServices()[0].panelActivity)
+    }
 
     private fun ServiceInfo(
             componentName: ComponentName,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceLegacySettingSyncerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceLegacySettingSyncerTest.kt
index 9200d72..de3bb6f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceLegacySettingSyncerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceLegacySettingSyncerTest.kt
@@ -72,6 +72,8 @@
         val resources: Resources = mock()
         whenever(resources.getStringArray(R.array.config_keyguardQuickAffordanceDefaults))
             .thenReturn(emptyArray())
+        whenever(resources.getBoolean(R.bool.custom_lockscreen_shortcuts_enabled))
+            .thenReturn(true)
         whenever(context.resources).thenReturn(resources)
 
         testDispatcher = UnconfinedTestDispatcher()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceLocalUserSelectionManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceLocalUserSelectionManagerTest.kt
index bad4b36..b2528c5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceLocalUserSelectionManagerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceLocalUserSelectionManagerTest.kt
@@ -66,6 +66,7 @@
     @Before
     fun setUp() {
         MockitoAnnotations.initMocks(this)
+        overrideResource(R.bool.custom_lockscreen_shortcuts_enabled, true)
         sharedPrefs = mutableMapOf()
         whenever(userFileManager.getSharedPreferences(anyString(), anyInt(), anyInt())).thenAnswer {
             val userId = it.arguments[2] as Int
@@ -86,6 +87,13 @@
 
     @After
     fun tearDown() {
+        mContext
+            .getOrCreateTestableResources()
+            .removeOverride(R.bool.custom_lockscreen_shortcuts_enabled)
+        mContext
+            .getOrCreateTestableResources()
+            .removeOverride(R.array.config_keyguardQuickAffordanceDefaults)
+
         Dispatchers.resetMain()
     }
 
@@ -358,6 +366,22 @@
         job.cancel()
     }
 
+    @Test
+    fun getSelections_alwaysReturnsDefaultsIfCustomShortcutsFeatureDisabled() {
+        overrideResource(R.bool.custom_lockscreen_shortcuts_enabled, false)
+        overrideResource(
+            R.array.config_keyguardQuickAffordanceDefaults,
+            arrayOf("leftTest:testShortcut1", "rightTest:testShortcut2")
+        )
+
+        assertThat(underTest.getSelections()).isEqualTo(
+            mapOf(
+                "leftTest" to listOf("testShortcut1"),
+                "rightTest" to listOf("testShortcut2"),
+            )
+        )
+    }
+
     private fun assertSelections(
         observed: Map<String, List<String>>?,
         expected: Map<String, List<String>>,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceRepositoryTest.kt
index 8dc04bd..ca7c5db 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceRepositoryTest.kt
@@ -47,6 +47,7 @@
 import kotlinx.coroutines.test.StandardTestDispatcher
 import kotlinx.coroutines.test.TestScope
 import kotlinx.coroutines.test.runTest
+import org.junit.After
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -70,6 +71,7 @@
 
     @Before
     fun setUp() {
+        overrideResource(R.bool.custom_lockscreen_shortcuts_enabled, true)
         context.resources.configuration.setLayoutDirection(Locale.US)
         config1 = FakeKeyguardQuickAffordanceConfig(FakeCustomizationProviderClient.AFFORDANCE_1)
         config2 = FakeKeyguardQuickAffordanceConfig(FakeCustomizationProviderClient.AFFORDANCE_2)
@@ -137,6 +139,13 @@
             )
     }
 
+    @After
+    fun tearDown() {
+        mContext
+            .getOrCreateTestableResources()
+            .removeOverride(R.bool.custom_lockscreen_shortcuts_enabled)
+    }
+
     @Test
     fun setSelections() =
         testScope.runTest {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt
index 5d2c3ed..895c1cd 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt
@@ -102,6 +102,8 @@
     fun setUp() {
         MockitoAnnotations.initMocks(this)
 
+        overrideResource(R.bool.custom_lockscreen_shortcuts_enabled, true)
+
         repository = FakeKeyguardRepository()
         repository.setKeyguardShowing(true)
 
@@ -200,7 +202,7 @@
                 devicePolicyManager = devicePolicyManager,
                 dockManager = dockManager,
                 backgroundDispatcher = testDispatcher,
-                appContext = mContext,
+                appContext = context,
             )
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskControllerTest.kt
index 079ef37..204077b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskControllerTest.kt
@@ -36,7 +36,6 @@
 import android.content.pm.PackageManager
 import android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DISABLED
 import android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_ENABLED
-import android.content.pm.ShortcutInfo
 import android.content.pm.ShortcutManager
 import android.content.pm.UserInfo
 import android.graphics.drawable.Icon
@@ -62,6 +61,7 @@
 import com.android.systemui.util.mockito.argumentCaptor
 import com.android.systemui.util.mockito.capture
 import com.android.systemui.util.mockito.eq
+import com.android.systemui.util.mockito.mock
 import com.android.systemui.util.mockito.whenever
 import com.android.systemui.util.mockito.withArgCaptor
 import com.android.systemui.util.settings.SecureSettings
@@ -115,7 +115,11 @@
 
         whenever(context.getString(R.string.note_task_button_label))
             .thenReturn(NOTE_TASK_SHORT_LABEL)
+        whenever(context.getString(eq(R.string.note_task_shortcut_long_label), any()))
+            .thenReturn(NOTE_TASK_LONG_LABEL)
         whenever(context.packageManager).thenReturn(packageManager)
+        whenever(packageManager.getApplicationInfo(any(), any<Int>())).thenReturn(mock())
+        whenever(packageManager.getApplicationLabel(any())).thenReturn(NOTE_TASK_LONG_LABEL)
         whenever(resolver.resolveInfo(any(), any(), any())).thenReturn(NOTE_TASK_INFO)
         whenever(userManager.isUserUnlocked).thenReturn(true)
         whenever(userManager.isUserUnlocked(any<Int>())).thenReturn(true)
@@ -706,19 +710,20 @@
             .isEqualTo(CreateNoteTaskShortcutActivity::class.java.name)
         verify(shortcutManager, never()).disableShortcuts(any())
         verify(shortcutManager).enableShortcuts(listOf(SHORTCUT_ID))
-        val actualShortcuts = argumentCaptor<List<ShortcutInfo>>()
-        verify(shortcutManager).updateShortcuts(actualShortcuts.capture())
-        val actualShortcut = actualShortcuts.value.first()
-        assertThat(actualShortcut.id).isEqualTo(SHORTCUT_ID)
-        assertThat(actualShortcut.intent).run {
-            hasComponentClass(LaunchNoteTaskActivity::class.java)
-            hasAction(ACTION_CREATE_NOTE)
+        val shortcutInfo = withArgCaptor { verify(shortcutManager).updateShortcuts(capture()) }
+        with(shortcutInfo.first()) {
+            assertThat(id).isEqualTo(SHORTCUT_ID)
+            assertThat(intent).run {
+                hasComponentClass(LaunchNoteTaskActivity::class.java)
+                hasAction(ACTION_CREATE_NOTE)
+            }
+            assertThat(shortLabel).isEqualTo(NOTE_TASK_SHORT_LABEL)
+            assertThat(longLabel).isEqualTo(NOTE_TASK_LONG_LABEL)
+            assertThat(isLongLived).isEqualTo(true)
+            assertThat(icon.resId).isEqualTo(R.drawable.ic_note_task_shortcut_widget)
+            assertThat(extras?.getString(EXTRA_SHORTCUT_BADGE_OVERRIDE_PACKAGE))
+                .isEqualTo(NOTE_TASK_PACKAGE_NAME)
         }
-        assertThat(actualShortcut.shortLabel).isEqualTo(NOTE_TASK_SHORT_LABEL)
-        assertThat(actualShortcut.isLongLived).isEqualTo(true)
-        assertThat(actualShortcut.icon.resId).isEqualTo(R.drawable.ic_note_task_shortcut_widget)
-        assertThat(actualShortcut.extras?.getString(EXTRA_SHORTCUT_BADGE_OVERRIDE_PACKAGE))
-            .isEqualTo(NOTE_TASK_PACKAGE_NAME)
     }
 
     @Test
@@ -893,7 +898,8 @@
     // endregion
 
     private companion object {
-        const val NOTE_TASK_SHORT_LABEL = "Notetaking"
+        const val NOTE_TASK_SHORT_LABEL = "Note-taking"
+        const val NOTE_TASK_LONG_LABEL = "Note-taking, App"
         const val NOTE_TASK_ACTIVITY_NAME = "NoteTaskActivity"
         const val NOTE_TASK_PACKAGE_NAME = "com.android.note.app"
         const val NOTE_TASK_UID = 123456
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenrecord/RecordingServiceTest.java b/packages/SystemUI/tests/src/com/android/systemui/screenrecord/RecordingServiceTest.java
index 3def6ba..f2812b5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenrecord/RecordingServiceTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenrecord/RecordingServiceTest.java
@@ -16,9 +16,11 @@
 
 package com.android.systemui.screenrecord;
 
+import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertThrows;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.doNothing;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.doThrow;
@@ -32,6 +34,7 @@
 import android.os.Binder;
 import android.os.Handler;
 import android.os.RemoteException;
+import android.os.UserHandle;
 import android.testing.AndroidTestingRunner;
 
 import androidx.test.filters.SmallTest;
@@ -41,7 +44,9 @@
 import com.android.systemui.media.MediaProjectionCaptureTarget;
 import com.android.systemui.plugins.ActivityStarter;
 import com.android.systemui.settings.UserContextProvider;
+import com.android.systemui.statusbar.SysuiStatusBarStateController;
 import com.android.systemui.statusbar.phone.KeyguardDismissUtil;
+import com.android.systemui.statusbar.policy.KeyguardStateController;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -77,20 +82,38 @@
     private UserContextProvider mUserContextTracker;
     @Captor
     private ArgumentCaptor<Runnable> mRunnableCaptor;
-    private KeyguardDismissUtil mKeyguardDismissUtil = new KeyguardDismissUtil() {
-        public void executeWhenUnlocked(ActivityStarter.OnDismissAction action,
-                boolean requiresShadeOpen) {
-            action.onDismiss();
-        }
-    };
+    @Mock
+    private KeyguardStateController mKeyguardStateController;
+    @Mock
+    private SysuiStatusBarStateController mStatusBarStateController;
+    @Mock
+    private ActivityStarter mActivityStarter;
+
+    private static final String PERMISSION_SELF = "com.android.systemui.permission.SELF";
+
+    private KeyguardDismissUtil mKeyguardDismissUtil = new KeyguardDismissUtil(
+            mKeyguardStateController, mStatusBarStateController, mActivityStarter);
 
     private RecordingService mRecordingService;
 
+    private class RecordingServiceTestable extends RecordingService {
+        RecordingServiceTestable(
+                RecordingController controller, Executor executor,
+                Handler handler, UiEventLogger uiEventLogger,
+                NotificationManager notificationManager,
+                UserContextProvider userContextTracker, KeyguardDismissUtil keyguardDismissUtil) {
+            super(controller, executor, handler,
+                    uiEventLogger, notificationManager, userContextTracker, keyguardDismissUtil);
+            attachBaseContext(mContext);
+        }
+    }
+
     @Before
     public void setUp() throws Exception {
         MockitoAnnotations.initMocks(this);
-        mRecordingService = Mockito.spy(new RecordingService(mController, mExecutor, mHandler,
-                mUiEventLogger, mNotificationManager, mUserContextTracker, mKeyguardDismissUtil));
+        mRecordingService = Mockito.spy(new RecordingServiceTestable(mController, mExecutor,
+                mHandler, mUiEventLogger, mNotificationManager,
+                mUserContextTracker, mKeyguardDismissUtil));
 
         // Return actual context info
         doReturn(mContext).when(mRecordingService).getApplicationContext();
@@ -160,8 +183,7 @@
         Intent startIntent = RecordingService.getStartIntent(mContext, 0, 0, false, null);
         mRecordingService.onStartCommand(startIntent, 0, 0);
 
-        // Then the state is set to not recording
-        verify(mController).updateState(false);
+        assertUpdateState(false);
     }
 
     @Test
@@ -179,7 +201,7 @@
 
         mRecordingService.onStopped();
 
-        verify(mController).updateState(false);
+        assertUpdateState(false);
     }
 
     @Test
@@ -224,8 +246,21 @@
         verify(mExecutor).execute(mRunnableCaptor.capture());
         mRunnableCaptor.getValue().run();
 
-        // Then the state is set to not recording and we cancel the notification
-        verify(mController).updateState(false);
+        assertUpdateState(false);
         verify(mNotificationManager).cancelAsUser(any(), anyInt(), any());
     }
+
+    private void assertUpdateState(boolean state) {
+        // Then the state is set to not recording, and we cancel the notification
+        // non SYSTEM user doesn't have the reference to the correct controller,
+        // so a broadcast is sent in case of non SYSTEM user.
+        if (UserHandle.USER_SYSTEM == mContext.getUserId()) {
+            verify(mController).updateState(state);
+        } else {
+            ArgumentCaptor<Intent> argumentCaptor = ArgumentCaptor.forClass(Intent.class);
+            verify(mRecordingService).sendBroadcast(argumentCaptor.capture(), eq(PERMISSION_SELF));
+            assertEquals(RecordingController.INTENT_UPDATE_STATE,
+                    argumentCaptor.getValue().getAction());
+        }
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialogTest.kt b/packages/SystemUI/tests/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialogTest.kt
index 5b094c9..07feedf 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialogTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialogTest.kt
@@ -104,6 +104,26 @@
         assertThat(visibility).isEqualTo(View.VISIBLE)
     }
 
+    @Test
+    fun showDialog_dialogIsShowing() {
+        dialog.show()
+
+        assertThat(dialog.isShowing).isTrue()
+    }
+
+    @Test
+    fun showDialog_cancelClicked_dialogIsDismissed() {
+        dialog.show()
+
+        clickOnCancel()
+
+        assertThat(dialog.isShowing).isFalse()
+    }
+
+    private fun clickOnCancel() {
+        dialog.requireViewById<View>(android.R.id.button2).performClick()
+    }
+
     private fun onSpinnerItemSelected(position: Int) {
         val spinner = dialog.requireViewById<Spinner>(R.id.screen_share_mode_spinner)
         spinner.onItemSelectedListener.onItemSelected(spinner, mock(), position, /* id= */ 0)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerTest.java
index 34d09a9..ff047aa 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerTest.java
@@ -580,6 +580,30 @@
         verify(mQs).setQsVisible(true);
     }
 
+    @Test
+    public void calculateBottomCornerRadius_scrimScaleMax() {
+        when(mScrimController.getBackScaling()).thenReturn(1.0f);
+        assertThat(mQsController.calculateBottomCornerRadius(0.0f)).isEqualTo(0);
+    }
+
+    @Test
+    public void calculateBottomCornerRadius_scrimScaleMin() {
+        when(mScrimController.getBackScaling())
+                .thenReturn(mNotificationPanelViewController.SHADE_BACK_ANIM_MIN_SCALE);
+        assertThat(mQsController.calculateBottomCornerRadius(0.0f))
+                .isEqualTo(mQsController.getScrimCornerRadius());
+    }
+
+    @Test
+    public void calculateBottomCornerRadius_scrimScaleCutoff() {
+        float ratio = 1 / mQsController.calculateBottomRadiusProgress();
+        float cutoffScale = 1 - mNotificationPanelViewController.SHADE_BACK_ANIM_MIN_SCALE / ratio;
+        when(mScrimController.getBackScaling())
+                .thenReturn(cutoffScale);
+        assertThat(mQsController.calculateBottomCornerRadius(0.0f))
+                .isEqualTo(mQsController.getScrimCornerRadius());
+    }
+
     private void lockScreen() {
         mQsController.setBarState(KEYGUARD);
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java
index fd9f6a7..4ed113f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java
@@ -287,7 +287,6 @@
     @Mock private StatusBarTouchableRegionManager mStatusBarTouchableRegionManager;
     @Mock private ScreenPinningRequest mScreenPinningRequest;
     @Mock private PluginDependencyProvider mPluginDependencyProvider;
-    @Mock private KeyguardDismissUtil mKeyguardDismissUtil;
     @Mock private ExtensionController mExtensionController;
     @Mock private UserInfoControllerImpl mUserInfoControllerImpl;
     @Mock private PhoneStatusBarPolicy mPhoneStatusBarPolicy;
@@ -516,7 +515,6 @@
                 mInitController,
                 new Handler(TestableLooper.get(this).getLooper()),
                 mPluginDependencyProvider,
-                mKeyguardDismissUtil,
                 mExtensionController,
                 mUserInfoControllerImpl,
                 mPhoneStatusBarPolicy,
@@ -1103,8 +1101,10 @@
         clearInvocations(mNotificationPanelViewController);
 
         mCentralSurfaces.mWakefulnessObserver.onStartedWakingUp();
-        verify(mDozeServiceHost).stopDozing();
+        verify(mDozeServiceHost, never()).stopDozing();
         verify(mNotificationPanelViewController).expand(eq(false));
+        mCentralSurfaces.mWakefulnessObserver.onFinishedWakingUp();
+        verify(mDozeServiceHost).stopDozing();
     }
 
     @Test
@@ -1118,6 +1118,8 @@
         mCentralSurfaces.setBouncerShowing(true);
         mCentralSurfaces.mWakefulnessObserver.onStartedWakingUp();
         verify(mNotificationPanelViewController, never()).expand(anyBoolean());
+        mCentralSurfaces.mWakefulnessObserver.onFinishedWakingUp();
+        verify(mDozeServiceHost).stopDozing();
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardDismissUtilTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardDismissUtilTest.java
new file mode 100644
index 0000000..b0aa2d3
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardDismissUtilTest.java
@@ -0,0 +1,85 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.phone;
+
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+import android.testing.AndroidTestingRunner;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.plugins.ActivityStarter;
+import com.android.systemui.statusbar.SysuiStatusBarStateController;
+import com.android.systemui.statusbar.policy.KeyguardStateController;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@RunWith(AndroidTestingRunner.class)
+@SmallTest
+public class KeyguardDismissUtilTest extends SysuiTestCase {
+    @Mock
+    private KeyguardStateController mKeyguardStateController;
+    @Mock
+    private SysuiStatusBarStateController mStatusBarStateController;
+    @Mock
+    private ActivityStarter mActivityStarter;
+    @Mock
+    private ActivityStarter.OnDismissAction mAction;
+
+    private KeyguardDismissUtil mKeyguardDismissUtil;
+
+    @Before
+    public void setUp() throws Exception {
+        MockitoAnnotations.initMocks(this);
+        mKeyguardDismissUtil = new KeyguardDismissUtil(
+                mKeyguardStateController, mStatusBarStateController, mActivityStarter);
+    }
+
+    @Test
+    public void testSetLeaveOpenOnKeyguardHideWhenKeyGuardStateControllerIsShowing() {
+        doReturn(true).when(mKeyguardStateController).isShowing();
+
+        mKeyguardDismissUtil.executeWhenUnlocked(mAction, true /* requiresShadeOpen */,
+                true /* afterKeyguardGone */);
+
+        verify(mStatusBarStateController).setLeaveOpenOnKeyguardHide(true);
+
+        verify(mActivityStarter).dismissKeyguardThenExecute(mAction, null, true);
+
+    }
+
+    @Test
+    public void testSetLeaveOpenOnKeyguardHideWhenKeyGuardStateControllerIsNotShowing() {
+        doReturn(false).when(mKeyguardStateController).isShowing();
+
+        mKeyguardDismissUtil.executeWhenUnlocked(mAction, true /* requiresShadeOpen */,
+                true /* afterKeyguardGone */);
+
+        //no interaction with mStatusBarStateController
+        verify(mStatusBarStateController, times(0)).setLeaveOpenOnKeyguardHide(true);
+
+        verify(mActivityStarter).dismissKeyguardThenExecute(mAction, null, true);
+
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
index e56f0d6..3eea93c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
@@ -352,6 +352,17 @@
     }
 
     @Test
+    public void onPanelExpansionChanged_neverTranslatesBouncerWhenGoingAway() {
+        when(mKeyguardStateController.isKeyguardGoingAway()).thenReturn(true);
+        mStatusBarKeyguardViewManager.onPanelExpansionChanged(
+                expansionEvent(
+                        /* fraction= */ EXPANSION_VISIBLE,
+                        /* expanded= */ true,
+                        /* tracking= */ false));
+        verify(mPrimaryBouncerInteractor, never()).setPanelExpansion(anyFloat());
+    }
+
+    @Test
     public void onPanelExpansionChanged_neverTranslatesBouncerWhenShowBouncer() {
         // Since KeyguardBouncer.EXPANSION_VISIBLE = 0 panel expansion, if the unlock is dismissing
         // the bouncer, there may be an onPanelExpansionChanged(0) call to collapse the panel
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SmartReplyViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SmartReplyViewTest.java
index 3b0d512..ae38958e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SmartReplyViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SmartReplyViewTest.java
@@ -51,9 +51,11 @@
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.plugins.ActivityStarter;
 import com.android.systemui.plugins.ActivityStarter.OnDismissAction;
+import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.shade.ShadeController;
 import com.android.systemui.statusbar.NotificationRemoteInputManager;
 import com.android.systemui.statusbar.SmartReplyController;
+import com.android.systemui.statusbar.SysuiStatusBarStateController;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
 import com.android.systemui.statusbar.phone.KeyguardDismissUtil;
@@ -107,14 +109,15 @@
 
     private SmartReplyInflaterImpl mSmartReplyInflater;
     private SmartActionInflaterImpl mSmartActionInflater;
+    private KeyguardDismissUtil mKeyguardDismissUtil;
 
     @Mock private SmartReplyConstants mConstants;
     @Mock private ActivityStarter mActivityStarter;
     @Mock private HeadsUpManager mHeadsUpManager;
     @Mock private NotificationRemoteInputManager mNotificationRemoteInputManager;
     @Mock private SmartReplyController mSmartReplyController;
-
-    private final KeyguardDismissUtil mKeyguardDismissUtil = new KeyguardDismissUtil();
+    @Mock private  KeyguardStateController mKeyguardStateController;
+    @Mock private  SysuiStatusBarStateController mStatusBarStateController;
 
     @Before
     public void setUp() {
@@ -122,12 +125,15 @@
         mReceiver = new BlockingQueueIntentReceiver();
         mContext.registerReceiver(mReceiver, new IntentFilter(TEST_ACTION),
                 Context.RECEIVER_EXPORTED_UNAUDITED);
-        mKeyguardDismissUtil.setDismissHandler((action, unused, afterKgGone) -> action.onDismiss());
+
         mDependency.injectMockDependency(KeyguardUpdateMonitor.class);
         mDependency.injectMockDependency(ShadeController.class);
         mDependency.injectMockDependency(NotificationRemoteInputManager.class);
         mDependency.injectTestDependency(ActivityStarter.class, mActivityStarter);
         mDependency.injectTestDependency(SmartReplyConstants.class, mConstants);
+        mDependency.injectTestDependency(KeyguardStateController.class, mKeyguardStateController);
+        mDependency.injectTestDependency(StatusBarStateController.class, mStatusBarStateController);
+
 
         // Any number of replies are fine.
         when(mConstants.getMinNumSystemGeneratedReplies()).thenReturn(0);
@@ -153,6 +159,13 @@
 
         mActionIcon = Icon.createWithResource(mContext, R.drawable.ic_person);
 
+        mKeyguardDismissUtil = new KeyguardDismissUtil(
+                mKeyguardStateController, mStatusBarStateController, mActivityStarter) {
+            public void executeWhenUnlocked(ActivityStarter.OnDismissAction action,
+                    boolean requiresShadeOpen, boolean afterKeyguardGone) {
+                action.onDismiss();
+            }
+        };
         mSmartReplyInflater = new SmartReplyInflaterImpl(
                 mConstants,
                 mKeyguardDismissUtil,
@@ -185,7 +198,17 @@
 
     @Test
     public void testSendSmartReply_keyguardCancelled() throws InterruptedException {
-        mKeyguardDismissUtil.setDismissHandler((action, unused, afterKgGone) -> { });
+        mKeyguardDismissUtil = new KeyguardDismissUtil(
+                mKeyguardStateController, mStatusBarStateController, mActivityStarter) {
+            public void executeWhenUnlocked(ActivityStarter.OnDismissAction action,
+                    boolean requiresShadeOpen, boolean afterKeyguardGone) { }};
+        mSmartReplyInflater = new SmartReplyInflaterImpl(
+                mConstants,
+                mKeyguardDismissUtil,
+                mNotificationRemoteInputManager,
+                mSmartReplyController,
+                mContext);
+
         setSmartReplies(TEST_CHOICES);
 
         mView.getChildAt(2).performClick();
@@ -196,9 +219,20 @@
     @Test
     public void testSendSmartReply_waitsForKeyguard() throws InterruptedException {
         AtomicReference<OnDismissAction> actionRef = new AtomicReference<>();
+        mKeyguardDismissUtil = new KeyguardDismissUtil(
+                mKeyguardStateController, mStatusBarStateController, mActivityStarter) {
+            public void executeWhenUnlocked(ActivityStarter.OnDismissAction action,
+                    boolean requiresShadeOpen, boolean afterKeyguardGone) {
+                actionRef.set(action);
+            }
+        };
+        mSmartReplyInflater = new SmartReplyInflaterImpl(
+                mConstants,
+                mKeyguardDismissUtil,
+                mNotificationRemoteInputManager,
+                mSmartReplyController,
+                mContext);
 
-        mKeyguardDismissUtil.setDismissHandler((action, unused, afterKgGone)
-                -> actionRef.set(action));
         setSmartReplies(TEST_CHOICES);
 
         mView.getChildAt(2).performClick();
diff --git a/services/autofill/java/com/android/server/autofill/AutofillManagerService.java b/services/autofill/java/com/android/server/autofill/AutofillManagerService.java
index fc758cb..1a57bc1 100644
--- a/services/autofill/java/com/android/server/autofill/AutofillManagerService.java
+++ b/services/autofill/java/com/android/server/autofill/AutofillManagerService.java
@@ -761,6 +761,18 @@
     }
 
     // Called by Shell command
+    boolean isFieldDetectionServiceEnabledForUser(@UserIdInt int userId) {
+        enforceCallingPermissionForManagement();
+        synchronized (mLock) {
+            final AutofillManagerServiceImpl service = getServiceForUserLocked(userId);
+            if (service != null) {
+                return service.isPccClassificationEnabled();
+            }
+        }
+        return false;
+    }
+
+    // Called by Shell command
     String getFieldDetectionServiceName(@UserIdInt int userId) {
         enforceCallingPermissionForManagement();
         return mFieldClassificationResolver.readServiceName(userId);
diff --git a/services/autofill/java/com/android/server/autofill/AutofillManagerServiceShellCommand.java b/services/autofill/java/com/android/server/autofill/AutofillManagerServiceShellCommand.java
index cd6de87..c66fb81 100644
--- a/services/autofill/java/com/android/server/autofill/AutofillManagerServiceShellCommand.java
+++ b/services/autofill/java/com/android/server/autofill/AutofillManagerServiceShellCommand.java
@@ -26,7 +26,6 @@
 import android.os.ShellCommand;
 import android.os.UserHandle;
 import android.service.autofill.AutofillFieldClassificationService.Scores;
-import android.text.TextUtils;
 import android.view.autofill.AutofillManager;
 
 import com.android.internal.os.IResultReceiver;
@@ -348,9 +347,7 @@
 
     private int isFieldDetectionServiceEnabled(PrintWriter pw) {
         final int userId = getNextIntArgRequired();
-        String name = mService.getFieldDetectionServiceName(userId);
-        boolean pccFlagEnabled = mService.isPccClassificationFlagEnabled();
-        boolean enabled = (!TextUtils.isEmpty(name)) && pccFlagEnabled;
+        boolean enabled = mService.isFieldDetectionServiceEnabledForUser(userId);
         pw.println(enabled);
         return 0;
     }
diff --git a/services/autofill/java/com/android/server/autofill/InlineSuggestionRequestConsumer.java b/services/autofill/java/com/android/server/autofill/InlineSuggestionRequestConsumer.java
new file mode 100644
index 0000000..a3efb25
--- /dev/null
+++ b/services/autofill/java/com/android/server/autofill/InlineSuggestionRequestConsumer.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.autofill;
+
+import android.util.Slog;
+import android.view.inputmethod.InlineSuggestionsRequest;
+
+import java.lang.ref.WeakReference;
+import java.util.function.Consumer;
+
+class InlineSuggestionRequestConsumer implements Consumer<InlineSuggestionsRequest> {
+
+    static final String TAG = "InlineSuggestionRequestConsumer";
+
+    private final WeakReference<Session.AssistDataReceiverImpl> mAssistDataReceiverWeakReference;
+    private final WeakReference<ViewState>  mViewStateWeakReference;
+
+    InlineSuggestionRequestConsumer(WeakReference<Session.AssistDataReceiverImpl>
+            assistDataReceiverWeakReference,
+            WeakReference<ViewState>  viewStateWeakReference) {
+        mAssistDataReceiverWeakReference = assistDataReceiverWeakReference;
+        mViewStateWeakReference = viewStateWeakReference;
+    }
+
+    @Override
+    public void accept(InlineSuggestionsRequest inlineSuggestionsRequest) {
+        Session.AssistDataReceiverImpl assistDataReceiver = mAssistDataReceiverWeakReference.get();
+        ViewState viewState = mViewStateWeakReference.get();
+        if (assistDataReceiver == null) {
+            Slog.wtf(TAG, "assistDataReceiver is null when accepting new inline suggestion"
+                    + "requests");
+            return;
+        }
+
+        if (viewState == null) {
+            Slog.wtf(TAG, "view state is null when accepting new inline suggestion requests");
+            return;
+        }
+        assistDataReceiver.handleInlineSuggestionRequest(inlineSuggestionsRequest, viewState);
+    }
+}
diff --git a/services/autofill/java/com/android/server/autofill/Session.java b/services/autofill/java/com/android/server/autofill/Session.java
index 44c5033..4576abb 100644
--- a/services/autofill/java/com/android/server/autofill/Session.java
+++ b/services/autofill/java/com/android/server/autofill/Session.java
@@ -323,7 +323,7 @@
      * Id of the View currently being displayed.
      */
     @GuardedBy("mLock")
-    @Nullable AutofillId mCurrentViewId;
+    private @Nullable AutofillId mCurrentViewId;
 
     @GuardedBy("mLock")
     private IAutoFillManagerClient mClient;
@@ -614,7 +614,7 @@
      * TODO(b/151867668): improve how asynchronous data dependencies are handled, without using
      * CountDownLatch.
      */
-    private final class AssistDataReceiverImpl extends IAssistDataReceiver.Stub {
+    final class AssistDataReceiverImpl extends IAssistDataReceiver.Stub {
         @GuardedBy("mLock")
         private boolean mWaitForInlineRequest;
         @GuardedBy("mLock")
@@ -629,17 +629,28 @@
             mPendingFillRequest = null;
             mWaitForInlineRequest = isInlineRequest;
             mPendingInlineSuggestionsRequest = null;
-            return isInlineRequest ? (inlineSuggestionsRequest) -> {
-                synchronized (mLock) {
-                    if (!mWaitForInlineRequest || mPendingInlineSuggestionsRequest != null) {
-                        return;
-                    }
-                    mWaitForInlineRequest = inlineSuggestionsRequest != null;
-                    mPendingInlineSuggestionsRequest = inlineSuggestionsRequest;
-                    maybeRequestFillLocked();
-                    viewState.resetState(ViewState.STATE_PENDING_CREATE_INLINE_REQUEST);
+            if (isInlineRequest) {
+                WeakReference<AssistDataReceiverImpl> assistDataReceiverWeakReference =
+                        new WeakReference<AssistDataReceiverImpl>(this);
+                WeakReference<ViewState> viewStateWeakReference =
+                        new WeakReference<ViewState>(viewState);
+                return new InlineSuggestionRequestConsumer(assistDataReceiverWeakReference,
+                    viewStateWeakReference);
+            }
+            return null;
+        }
+
+        void handleInlineSuggestionRequest(InlineSuggestionsRequest inlineSuggestionsRequest,
+                ViewState viewState) {
+            synchronized (mLock) {
+                if (!mWaitForInlineRequest || mPendingInlineSuggestionsRequest != null) {
+                    return;
                 }
-            } : null;
+                mWaitForInlineRequest = inlineSuggestionsRequest != null;
+                mPendingInlineSuggestionsRequest = inlineSuggestionsRequest;
+                maybeRequestFillLocked();
+                viewState.resetState(ViewState.STATE_PENDING_CREATE_INLINE_REQUEST);
+            }
         }
 
         @GuardedBy("mLock")
diff --git a/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java b/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java
index 61fc32d..51359ad 100644
--- a/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java
+++ b/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java
@@ -163,13 +163,35 @@
     private boolean mDisabledByDeviceConfig;
 
     // Device-config settings that are cached and passed back to apps
-    @GuardedBy("mLock") int mDevCfgLoggingLevel;
-    @GuardedBy("mLock") int mDevCfgMaxBufferSize;
-    @GuardedBy("mLock") int mDevCfgIdleFlushingFrequencyMs;
-    @GuardedBy("mLock") int mDevCfgTextChangeFlushingFrequencyMs;
-    @GuardedBy("mLock") int mDevCfgLogHistorySize;
-    @GuardedBy("mLock") int mDevCfgIdleUnbindTimeoutMs;
-    @GuardedBy("mLock") boolean mDevCfgDisableFlushForViewTreeAppearing;
+    @GuardedBy("mLock")
+    int mDevCfgLoggingLevel;
+
+    @GuardedBy("mLock")
+    int mDevCfgMaxBufferSize;
+
+    @GuardedBy("mLock")
+    int mDevCfgIdleFlushingFrequencyMs;
+
+    @GuardedBy("mLock")
+    int mDevCfgTextChangeFlushingFrequencyMs;
+
+    @GuardedBy("mLock")
+    int mDevCfgLogHistorySize;
+
+    @GuardedBy("mLock")
+    int mDevCfgIdleUnbindTimeoutMs;
+
+    @GuardedBy("mLock")
+    boolean mDevCfgDisableFlushForViewTreeAppearing;
+
+    @GuardedBy("mLock")
+    boolean mDevCfgEnableContentProtectionReceiver;
+
+    @GuardedBy("mLock")
+    int mDevCfgContentProtectionAppsBlocklistSize;
+
+    @GuardedBy("mLock")
+    int mDevCfgContentProtectionBufferSize;
 
     private final Executor mDataShareExecutor = Executors.newCachedThreadPool();
     private final Handler mHandler = new Handler(Looper.getMainLooper());
@@ -362,6 +384,11 @@
                 case ContentCaptureManager.DEVICE_CONFIG_PROPERTY_IDLE_UNBIND_TIMEOUT:
                 case ContentCaptureManager
                         .DEVICE_CONFIG_PROPERTY_DISABLE_FLUSH_FOR_VIEW_TREE_APPEARING:
+                case ContentCaptureManager
+                        .DEVICE_CONFIG_PROPERTY_ENABLE_CONTENT_PROTECTION_RECEIVER:
+                case ContentCaptureManager.DEVICE_CONFIG_PROPERTY_CONTENT_PROTECTION_BUFFER_SIZE:
+                case ContentCaptureManager
+                        .DEVICE_CONFIG_PROPERTY_CONTENT_PROTECTION_APPS_BLOCKLIST_SIZE:
                     setFineTuneParamsFromDeviceConfig();
                     return;
                 default:
@@ -372,39 +399,78 @@
 
     private void setFineTuneParamsFromDeviceConfig() {
         synchronized (mLock) {
-            mDevCfgMaxBufferSize = DeviceConfig.getInt(
-                    DeviceConfig.NAMESPACE_CONTENT_CAPTURE,
-                    ContentCaptureManager.DEVICE_CONFIG_PROPERTY_MAX_BUFFER_SIZE,
-                    ContentCaptureManager.DEFAULT_MAX_BUFFER_SIZE);
-            mDevCfgIdleFlushingFrequencyMs = DeviceConfig.getInt(
-                    DeviceConfig.NAMESPACE_CONTENT_CAPTURE,
-                    ContentCaptureManager.DEVICE_CONFIG_PROPERTY_IDLE_FLUSH_FREQUENCY,
-                    ContentCaptureManager.DEFAULT_IDLE_FLUSHING_FREQUENCY_MS);
-            mDevCfgTextChangeFlushingFrequencyMs = DeviceConfig.getInt(
-                    DeviceConfig.NAMESPACE_CONTENT_CAPTURE,
-                    ContentCaptureManager.DEVICE_CONFIG_PROPERTY_TEXT_CHANGE_FLUSH_FREQUENCY,
-                    ContentCaptureManager.DEFAULT_TEXT_CHANGE_FLUSHING_FREQUENCY_MS);
-            mDevCfgLogHistorySize = DeviceConfig.getInt(
-                    DeviceConfig.NAMESPACE_CONTENT_CAPTURE,
-                    ContentCaptureManager.DEVICE_CONFIG_PROPERTY_LOG_HISTORY_SIZE, 20);
-            mDevCfgIdleUnbindTimeoutMs = DeviceConfig.getInt(
-                    DeviceConfig.NAMESPACE_CONTENT_CAPTURE,
-                    ContentCaptureManager.DEVICE_CONFIG_PROPERTY_IDLE_UNBIND_TIMEOUT,
-                    (int) AbstractRemoteService.PERMANENT_BOUND_TIMEOUT_MS);
-            mDevCfgDisableFlushForViewTreeAppearing = DeviceConfig.getBoolean(
-                    DeviceConfig.NAMESPACE_CONTENT_CAPTURE,
-                    ContentCaptureManager
-                        .DEVICE_CONFIG_PROPERTY_DISABLE_FLUSH_FOR_VIEW_TREE_APPEARING,
-                    false);
+            mDevCfgMaxBufferSize =
+                    DeviceConfig.getInt(
+                            DeviceConfig.NAMESPACE_CONTENT_CAPTURE,
+                            ContentCaptureManager.DEVICE_CONFIG_PROPERTY_MAX_BUFFER_SIZE,
+                            ContentCaptureManager.DEFAULT_MAX_BUFFER_SIZE);
+            mDevCfgIdleFlushingFrequencyMs =
+                    DeviceConfig.getInt(
+                            DeviceConfig.NAMESPACE_CONTENT_CAPTURE,
+                            ContentCaptureManager.DEVICE_CONFIG_PROPERTY_IDLE_FLUSH_FREQUENCY,
+                            ContentCaptureManager.DEFAULT_IDLE_FLUSHING_FREQUENCY_MS);
+            mDevCfgTextChangeFlushingFrequencyMs =
+                    DeviceConfig.getInt(
+                            DeviceConfig.NAMESPACE_CONTENT_CAPTURE,
+                            ContentCaptureManager
+                                    .DEVICE_CONFIG_PROPERTY_TEXT_CHANGE_FLUSH_FREQUENCY,
+                            ContentCaptureManager.DEFAULT_TEXT_CHANGE_FLUSHING_FREQUENCY_MS);
+            mDevCfgLogHistorySize =
+                    DeviceConfig.getInt(
+                            DeviceConfig.NAMESPACE_CONTENT_CAPTURE,
+                            ContentCaptureManager.DEVICE_CONFIG_PROPERTY_LOG_HISTORY_SIZE,
+                            20);
+            mDevCfgIdleUnbindTimeoutMs =
+                    DeviceConfig.getInt(
+                            DeviceConfig.NAMESPACE_CONTENT_CAPTURE,
+                            ContentCaptureManager.DEVICE_CONFIG_PROPERTY_IDLE_UNBIND_TIMEOUT,
+                            (int) AbstractRemoteService.PERMANENT_BOUND_TIMEOUT_MS);
+            mDevCfgDisableFlushForViewTreeAppearing =
+                    DeviceConfig.getBoolean(
+                            DeviceConfig.NAMESPACE_CONTENT_CAPTURE,
+                            ContentCaptureManager
+                                    .DEVICE_CONFIG_PROPERTY_DISABLE_FLUSH_FOR_VIEW_TREE_APPEARING,
+                            false);
+            mDevCfgEnableContentProtectionReceiver =
+                    DeviceConfig.getBoolean(
+                            DeviceConfig.NAMESPACE_CONTENT_CAPTURE,
+                            ContentCaptureManager
+                                    .DEVICE_CONFIG_PROPERTY_ENABLE_CONTENT_PROTECTION_RECEIVER,
+                            ContentCaptureManager.DEFAULT_ENABLE_CONTENT_PROTECTION_RECEIVER);
+            mDevCfgContentProtectionAppsBlocklistSize =
+                    DeviceConfig.getInt(
+                            DeviceConfig.NAMESPACE_CONTENT_CAPTURE,
+                            ContentCaptureManager
+                                    .DEVICE_CONFIG_PROPERTY_CONTENT_PROTECTION_APPS_BLOCKLIST_SIZE,
+                            ContentCaptureManager.DEFAULT_CONTENT_PROTECTION_APPS_BLOCKLIST_SIZE);
+            mDevCfgContentProtectionBufferSize =
+                    DeviceConfig.getInt(
+                            DeviceConfig.NAMESPACE_CONTENT_CAPTURE,
+                            ContentCaptureManager
+                                    .DEVICE_CONFIG_PROPERTY_CONTENT_PROTECTION_BUFFER_SIZE,
+                            ContentCaptureManager.DEFAULT_CONTENT_PROTECTION_BUFFER_SIZE);
             if (verbose) {
-                Slog.v(TAG, "setFineTuneParamsFromDeviceConfig(): "
-                        + "bufferSize=" + mDevCfgMaxBufferSize
-                        + ", idleFlush=" + mDevCfgIdleFlushingFrequencyMs
-                        + ", textFluxh=" + mDevCfgTextChangeFlushingFrequencyMs
-                        + ", logHistory=" + mDevCfgLogHistorySize
-                        + ", idleUnbindTimeoutMs=" + mDevCfgIdleUnbindTimeoutMs
-                        + ", disableFlushForViewTreeAppearing="
-                        + mDevCfgDisableFlushForViewTreeAppearing);
+                Slog.v(
+                        TAG,
+                        "setFineTuneParamsFromDeviceConfig(): "
+                                + "bufferSize="
+                                + mDevCfgMaxBufferSize
+                                + ", idleFlush="
+                                + mDevCfgIdleFlushingFrequencyMs
+                                + ", textFluxh="
+                                + mDevCfgTextChangeFlushingFrequencyMs
+                                + ", logHistory="
+                                + mDevCfgLogHistorySize
+                                + ", idleUnbindTimeoutMs="
+                                + mDevCfgIdleUnbindTimeoutMs
+                                + ", disableFlushForViewTreeAppearing="
+                                + mDevCfgDisableFlushForViewTreeAppearing
+                                + ", enableContentProtectionReceiver="
+                                + mDevCfgEnableContentProtectionReceiver
+                                + ", contentProtectionAppsBlocklistSize="
+                                + mDevCfgContentProtectionAppsBlocklistSize
+                                + ", contentProtectionBufferSize="
+                                + mDevCfgContentProtectionBufferSize);
             }
         }
     }
@@ -645,21 +711,46 @@
 
         final String prefix2 = prefix + "  ";
 
-        pw.print(prefix); pw.print("Users disabled by Settings: "); pw.println(mDisabledBySettings);
-        pw.print(prefix); pw.println("DeviceConfig Settings: ");
-        pw.print(prefix2); pw.print("disabled: "); pw.println(mDisabledByDeviceConfig);
-        pw.print(prefix2); pw.print("loggingLevel: "); pw.println(mDevCfgLoggingLevel);
-        pw.print(prefix2); pw.print("maxBufferSize: "); pw.println(mDevCfgMaxBufferSize);
-        pw.print(prefix2); pw.print("idleFlushingFrequencyMs: ");
+        pw.print(prefix);
+        pw.print("Users disabled by Settings: ");
+        pw.println(mDisabledBySettings);
+        pw.print(prefix);
+        pw.println("DeviceConfig Settings: ");
+        pw.print(prefix2);
+        pw.print("disabled: ");
+        pw.println(mDisabledByDeviceConfig);
+        pw.print(prefix2);
+        pw.print("loggingLevel: ");
+        pw.println(mDevCfgLoggingLevel);
+        pw.print(prefix2);
+        pw.print("maxBufferSize: ");
+        pw.println(mDevCfgMaxBufferSize);
+        pw.print(prefix2);
+        pw.print("idleFlushingFrequencyMs: ");
         pw.println(mDevCfgIdleFlushingFrequencyMs);
-        pw.print(prefix2); pw.print("textChangeFlushingFrequencyMs: ");
+        pw.print(prefix2);
+        pw.print("textChangeFlushingFrequencyMs: ");
         pw.println(mDevCfgTextChangeFlushingFrequencyMs);
-        pw.print(prefix2); pw.print("logHistorySize: "); pw.println(mDevCfgLogHistorySize);
-        pw.print(prefix2); pw.print("idleUnbindTimeoutMs: ");
+        pw.print(prefix2);
+        pw.print("logHistorySize: ");
+        pw.println(mDevCfgLogHistorySize);
+        pw.print(prefix2);
+        pw.print("idleUnbindTimeoutMs: ");
         pw.println(mDevCfgIdleUnbindTimeoutMs);
-        pw.print(prefix2); pw.print("disableFlushForViewTreeAppearing: ");
+        pw.print(prefix2);
+        pw.print("disableFlushForViewTreeAppearing: ");
         pw.println(mDevCfgDisableFlushForViewTreeAppearing);
-        pw.print(prefix); pw.println("Global Options:");
+        pw.print(prefix2);
+        pw.print("enableContentProtectionReceiver: ");
+        pw.println(mDevCfgEnableContentProtectionReceiver);
+        pw.print(prefix2);
+        pw.print("contentProtectionAppsBlocklistSize: ");
+        pw.println(mDevCfgContentProtectionAppsBlocklistSize);
+        pw.print(prefix2);
+        pw.print("contentProtectionBufferSize: ");
+        pw.println(mDevCfgContentProtectionBufferSize);
+        pw.print(prefix);
+        pw.println("Global Options:");
         mGlobalContentCaptureOptions.dump(prefix2, pw);
     }
 
@@ -1019,11 +1110,19 @@
             }
 
             synchronized (mLock) {
-                final ContentCaptureOptions options = new ContentCaptureOptions(mDevCfgLoggingLevel,
-                        mDevCfgMaxBufferSize, mDevCfgIdleFlushingFrequencyMs,
-                        mDevCfgTextChangeFlushingFrequencyMs, mDevCfgLogHistorySize,
-                        mDevCfgDisableFlushForViewTreeAppearing,
-                        whitelistedComponents);
+                final ContentCaptureOptions options =
+                        new ContentCaptureOptions(
+                                mDevCfgLoggingLevel,
+                                mDevCfgMaxBufferSize,
+                                mDevCfgIdleFlushingFrequencyMs,
+                                mDevCfgTextChangeFlushingFrequencyMs,
+                                mDevCfgLogHistorySize,
+                                mDevCfgDisableFlushForViewTreeAppearing,
+                                /* enableReceiver= */ true,
+                                new ContentCaptureOptions.ContentProtectionOptions(
+                                        mDevCfgEnableContentProtectionReceiver,
+                                        mDevCfgContentProtectionBufferSize),
+                                whitelistedComponents);
                 if (verbose) Slog.v(TAG, "getOptionsForPackage(" + packageName + "): " + options);
                 return options;
             }
diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java
index 31d60f2..00dbb97 100644
--- a/services/core/java/com/android/server/am/ActiveServices.java
+++ b/services/core/java/com/android/server/am/ActiveServices.java
@@ -3797,7 +3797,9 @@
             }
             clientPsr.addConnection(c);
             c.startAssociationIfNeeded();
-            if (c.hasFlag(Context.BIND_ABOVE_CLIENT)) {
+            // Don't set hasAboveClient if binding to self to prevent modifyRawOomAdj() from
+            // dropping the process' adjustment level.
+            if (b.client != s.app && c.hasFlag(Context.BIND_ABOVE_CLIENT)) {
                 clientPsr.setHasAboveClient(true);
             }
             if (c.hasFlag(BIND_ALLOW_WHITELIST_MANAGEMENT)) {
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 3d02c96..460ce44 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -3393,7 +3393,7 @@
                 mProcessList.noteAppKill(app, ApplicationExitInfo.REASON_OTHER,
                         ApplicationExitInfo.SUBREASON_UNKNOWN, reason);
             }
-            ProcessList.killProcessGroup(app.uid, pid);
+            app.killProcessGroupIfNecessaryLocked(true);
             synchronized (mProcLock) {
                 app.setKilled(true);
             }
diff --git a/services/core/java/com/android/server/am/OomAdjuster.java b/services/core/java/com/android/server/am/OomAdjuster.java
index 85a0185..6c3f01e 100644
--- a/services/core/java/com/android/server/am/OomAdjuster.java
+++ b/services/core/java/com/android/server/am/OomAdjuster.java
@@ -3253,7 +3253,11 @@
                 // {@link SCHED_GROUP_TOP_APP}. We don't check render thread because it
                 // is not ready when attaching.
                 app.getWindowProcessController().onTopProcChanged();
-                setThreadPriority(app.getPid(), THREAD_PRIORITY_TOP_APP_BOOST);
+                if (mService.mUseFifoUiScheduling) {
+                    mService.scheduleAsFifoPriority(app.getPid(), true);
+                } else {
+                    setThreadPriority(app.getPid(), THREAD_PRIORITY_TOP_APP_BOOST);
+                }
                 initialSchedGroup = SCHED_GROUP_TOP_APP;
             } catch (Exception e) {
                 Slog.w(TAG, "Failed to pre-set top priority to " + app + " " + e);
diff --git a/services/core/java/com/android/server/am/ProcessRecord.java b/services/core/java/com/android/server/am/ProcessRecord.java
index 335d676..d6495c7 100644
--- a/services/core/java/com/android/server/am/ProcessRecord.java
+++ b/services/core/java/com/android/server/am/ProcessRecord.java
@@ -1197,26 +1197,7 @@
                 EventLog.writeEvent(EventLogTags.AM_KILL,
                         userId, mPid, processName, mState.getSetAdj(), reason);
                 Process.killProcessQuiet(mPid);
-                final boolean killProcessGroup;
-                if (mHostingRecord != null
-                        && (mHostingRecord.usesWebviewZygote() || mHostingRecord.usesAppZygote())) {
-                    synchronized (ProcessRecord.this) {
-                        killProcessGroup = mProcessGroupCreated;
-                        if (!killProcessGroup) {
-                            // The process group hasn't been created, request to skip it.
-                            mSkipProcessGroupCreation = true;
-                        }
-                    }
-                } else {
-                    killProcessGroup = true;
-                }
-                if (killProcessGroup) {
-                    if (asyncKPG) {
-                        ProcessList.killProcessGroup(uid, mPid);
-                    } else {
-                        Process.sendSignalToProcessGroup(uid, mPid, OsConstants.SIGKILL);
-                    }
-                }
+                killProcessGroupIfNecessaryLocked(asyncKPG);
             } else {
                 mPendingStart = false;
             }
@@ -1231,6 +1212,30 @@
         }
     }
 
+    @GuardedBy("mService")
+    void killProcessGroupIfNecessaryLocked(boolean async) {
+        final boolean killProcessGroup;
+        if (mHostingRecord != null
+                && (mHostingRecord.usesWebviewZygote() || mHostingRecord.usesAppZygote())) {
+            synchronized (ProcessRecord.this) {
+                killProcessGroup = mProcessGroupCreated;
+                if (!killProcessGroup) {
+                    // The process group hasn't been created, request to skip it.
+                    mSkipProcessGroupCreation = true;
+                }
+            }
+        } else {
+            killProcessGroup = true;
+        }
+        if (killProcessGroup) {
+            if (async) {
+                ProcessList.killProcessGroup(uid, mPid);
+            } else {
+                Process.sendSignalToProcessGroup(uid, mPid, OsConstants.SIGKILL);
+            }
+        }
+    }
+
     @Override
     public void dumpDebug(ProtoOutputStream proto, long fieldId) {
         dumpDebug(proto, fieldId, -1);
diff --git a/services/core/java/com/android/server/am/ProcessServiceRecord.java b/services/core/java/com/android/server/am/ProcessServiceRecord.java
index 81d0b6a..7ff6d11 100644
--- a/services/core/java/com/android/server/am/ProcessServiceRecord.java
+++ b/services/core/java/com/android/server/am/ProcessServiceRecord.java
@@ -341,7 +341,8 @@
         mHasAboveClient = false;
         for (int i = mConnections.size() - 1; i >= 0; i--) {
             ConnectionRecord cr = mConnections.valueAt(i);
-            if (cr.hasFlag(Context.BIND_ABOVE_CLIENT)) {
+            if (cr.binding.service.app.mServices != this
+                    && cr.hasFlag(Context.BIND_ABOVE_CLIENT)) {
                 mHasAboveClient = true;
                 break;
             }
diff --git a/services/core/java/com/android/server/biometrics/log/BiometricContextProvider.java b/services/core/java/com/android/server/biometrics/log/BiometricContextProvider.java
index 21ade1b..fc3d7c8 100644
--- a/services/core/java/com/android/server/biometrics/log/BiometricContextProvider.java
+++ b/services/core/java/com/android/server/biometrics/log/BiometricContextProvider.java
@@ -156,7 +156,7 @@
     @Override
     public OperationContextExt updateContext(@NonNull OperationContextExt operationContext,
             boolean isCryptoOperation) {
-        return operationContext.update(this);
+        return operationContext.update(this, isCryptoOperation);
     }
 
     @Nullable
@@ -238,7 +238,7 @@
 
     private void notifySubscribers() {
         mSubscribers.forEach((context, consumer) -> {
-            consumer.accept(context.update(this).toAidlContext());
+            consumer.accept(context.update(this, context.isCrypto()).toAidlContext());
         });
     }
 
diff --git a/services/core/java/com/android/server/biometrics/log/OperationContextExt.java b/services/core/java/com/android/server/biometrics/log/OperationContextExt.java
index 4d821e9..4a10e8e 100644
--- a/services/core/java/com/android/server/biometrics/log/OperationContextExt.java
+++ b/services/core/java/com/android/server/biometrics/log/OperationContextExt.java
@@ -241,9 +241,10 @@
     }
 
     /** Update this object with the latest values from the given context. */
-    OperationContextExt update(@NonNull BiometricContext biometricContext) {
+    OperationContextExt update(@NonNull BiometricContext biometricContext, boolean isCrypto) {
         mAidlContext.isAod = biometricContext.isAod();
         mAidlContext.displayState = toAidlDisplayState(biometricContext.getDisplayState());
+        mAidlContext.isCrypto = isCrypto;
         setFirstSessionId(biometricContext);
 
         mIsDisplayOn = biometricContext.isDisplayOn();
diff --git a/services/core/java/com/android/server/biometrics/sensors/BiometricScheduler.java b/services/core/java/com/android/server/biometrics/sensors/BiometricScheduler.java
index b474cad..aa5f9fa 100644
--- a/services/core/java/com/android/server/biometrics/sensors/BiometricScheduler.java
+++ b/services/core/java/com/android/server/biometrics/sensors/BiometricScheduler.java
@@ -32,6 +32,7 @@
 import android.util.proto.ProtoOutputStream;
 
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.modules.expresslog.Counter;
 import com.android.server.biometrics.BiometricSchedulerProto;
 import com.android.server.biometrics.BiometricsProto;
 import com.android.server.biometrics.sensors.fingerprint.GestureAvailabilityDispatcher;
@@ -569,9 +570,10 @@
         if (mCurrentOperation == null) {
             return;
         }
-        final BiometricSchedulerOperation mOperation = mCurrentOperation;
+        final BiometricSchedulerOperation operation = mCurrentOperation;
         mHandler.postDelayed(() -> {
-            if (mOperation == mCurrentOperation) {
+            if (operation == mCurrentOperation) {
+                Counter.logIncrement("biometric.scheduler_watchdog_triggered_count");
                 clearScheduler();
             }
         }, 10000);
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java b/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java
index c2994a9..6f26e7b 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java
@@ -415,6 +415,11 @@
                         }
                     }
                 }
+
+                @Override
+                public void onError(int error, int vendorCode) throws RemoteException {
+                    receiver.onError(error, vendorCode);
+                }
             };
 
             // This effectively iterates through all sensors, but has to do so by finding all
diff --git a/services/core/java/com/android/server/display/AutomaticBrightnessController.java b/services/core/java/com/android/server/display/AutomaticBrightnessController.java
index 2e8e7c2..d647757 100644
--- a/services/core/java/com/android/server/display/AutomaticBrightnessController.java
+++ b/services/core/java/com/android/server/display/AutomaticBrightnessController.java
@@ -335,11 +335,8 @@
         // Initialize to active (normal) screen brightness mode
         switchToInteractiveScreenBrightnessMode();
 
-        if (userLux != BrightnessMappingStrategy.NO_USER_LUX
-                && userBrightness != BrightnessMappingStrategy.NO_USER_BRIGHTNESS) {
-            // Use the given short-term model
-            setScreenBrightnessByUser(userLux, userBrightness);
-        }
+        // Use the given short-term model
+        setScreenBrightnessByUser(userLux, userBrightness);
     }
 
     /**
@@ -522,6 +519,10 @@
     }
 
     private boolean setScreenBrightnessByUser(float lux, float brightness) {
+        if (lux == BrightnessMappingStrategy.NO_USER_LUX
+                || brightness == BrightnessMappingStrategy.NO_USER_BRIGHTNESS) {
+            return false;
+        }
         mCurrentBrightnessMapper.addUserDataPoint(lux, brightness);
         mShortTermModel.setUserBrightness(lux, brightness);
         return true;
@@ -1237,14 +1238,14 @@
         // light.
         // The anchor determines what were the light levels when the user has set their preference,
         // and we use a relative threshold to determine when to revert to the OEM curve.
-        private float mAnchor = -1f;
-        private float mBrightness;
-        private boolean mIsValid = true;
+        private float mAnchor = BrightnessMappingStrategy.NO_USER_LUX;
+        private float mBrightness = BrightnessMappingStrategy.NO_USER_BRIGHTNESS;
+        private boolean mIsValid = false;
 
         private void reset() {
-            mAnchor = -1f;
-            mBrightness = -1f;
-            mIsValid = true;
+            mAnchor = BrightnessMappingStrategy.NO_USER_LUX;
+            mBrightness = BrightnessMappingStrategy.NO_USER_BRIGHTNESS;
+            mIsValid = false;
         }
 
         private void invalidate() {
diff --git a/services/core/java/com/android/server/display/BrightnessSetting.java b/services/core/java/com/android/server/display/BrightnessSetting.java
index de42370..651828b 100644
--- a/services/core/java/com/android/server/display/BrightnessSetting.java
+++ b/services/core/java/com/android/server/display/BrightnessSetting.java
@@ -40,6 +40,7 @@
 
     private final LogicalDisplay mLogicalDisplay;
 
+    private int mUserSerial;
     private final Handler mHandler = new Handler(Looper.getMainLooper()) {
         @Override
         public void handleMessage(Message msg) {
@@ -56,13 +57,15 @@
     @GuardedBy("mSyncRoot")
     private float mBrightness;
 
-    BrightnessSetting(@NonNull PersistentDataStore persistentDataStore,
+    BrightnessSetting(int userSerial,
+            @NonNull PersistentDataStore persistentDataStore,
             @NonNull LogicalDisplay logicalDisplay,
             DisplayManagerService.SyncRoot syncRoot) {
         mPersistentDataStore = persistentDataStore;
         mLogicalDisplay = logicalDisplay;
+        mUserSerial = userSerial;
         mBrightness = mPersistentDataStore.getBrightness(
-                mLogicalDisplay.getPrimaryDisplayDeviceLocked());
+                mLogicalDisplay.getPrimaryDisplayDeviceLocked(), userSerial);
         mSyncRoot = syncRoot;
     }
 
@@ -96,8 +99,13 @@
         mListeners.remove(l);
     }
 
+    /** Sets the user serial for the brightness setting */
+    public void setUserSerial(int userSerial) {
+        mUserSerial = userSerial;
+    }
+
     /**
-     * Sets the brigtness and broadcasts the change to the listeners.
+     * Sets the brightness and broadcasts the change to the listeners.
      * @param brightness The value to which the brightness is to be set.
      */
     public void setBrightness(float brightness) {
@@ -112,7 +120,8 @@
             // changed.
             if (brightness != mBrightness) {
                 mPersistentDataStore.setBrightness(mLogicalDisplay.getPrimaryDisplayDeviceLocked(),
-                        brightness);
+                        brightness, mUserSerial
+                );
             }
             mBrightness = brightness;
             int toSend = Float.floatToIntBits(mBrightness);
diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java
index 3844529..be65c53 100644
--- a/services/core/java/com/android/server/display/DisplayManagerService.java
+++ b/services/core/java/com/android/server/display/DisplayManagerService.java
@@ -652,6 +652,12 @@
                             logicalDisplay.getPrimaryDisplayDeviceLocked().getUniqueId(),
                             userSerial);
                     dpc.setBrightnessConfiguration(config, /* shouldResetShortTermModel= */ true);
+                    // change the brightness value according to the selected user.
+                    final DisplayDevice device = logicalDisplay.getPrimaryDisplayDeviceLocked();
+                    if (device != null) {
+                        dpc.setBrightness(
+                                mPersistentDataStore.getBrightness(device, userSerial), userSerial);
+                    }
                 }
                 dpc.onSwitchUser(newUserId);
             });
@@ -3134,8 +3140,9 @@
             mBrightnessTracker = new BrightnessTracker(mContext, null);
         }
 
-        final BrightnessSetting brightnessSetting = new BrightnessSetting(mPersistentDataStore,
-                display, mSyncRoot);
+        final int userSerial = getUserManager().getUserSerialNumber(mContext.getUserId());
+        final BrightnessSetting brightnessSetting = new BrightnessSetting(userSerial,
+                mPersistentDataStore, display, mSyncRoot);
         final DisplayPowerControllerInterface displayPowerController;
 
         // If display is internal and has a HighBrightnessModeMetadata mapping, use that.
diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java
index 2b5523a..7d8bde9 100644
--- a/services/core/java/com/android/server/display/DisplayPowerController.java
+++ b/services/core/java/com/android/server/display/DisplayPowerController.java
@@ -2664,9 +2664,10 @@
     }
 
     @Override
-    public void setBrightness(float brightnessValue) {
+    public void setBrightness(float brightnessValue, int userSerial) {
         // Update the setting, which will eventually call back into DPC to have us actually update
         // the display with the new value.
+        mBrightnessSetting.setUserSerial(userSerial);
         mBrightnessSetting.setBrightness(brightnessValue);
         if (mDisplayId == Display.DEFAULT_DISPLAY && mPersistBrightnessNitsForDefaultDisplay) {
             float nits = convertToNits(brightnessValue);
diff --git a/services/core/java/com/android/server/display/DisplayPowerController2.java b/services/core/java/com/android/server/display/DisplayPowerController2.java
index cc9f539..040cecc 100644
--- a/services/core/java/com/android/server/display/DisplayPowerController2.java
+++ b/services/core/java/com/android/server/display/DisplayPowerController2.java
@@ -2172,8 +2172,8 @@
     }
 
     @Override
-    public void setBrightness(float brightnessValue) {
-        mDisplayBrightnessController.setBrightness(brightnessValue);
+    public void setBrightness(float brightnessValue, int userSerial) {
+        mDisplayBrightnessController.setBrightness(brightnessValue, userSerial);
     }
 
     @Override
diff --git a/services/core/java/com/android/server/display/DisplayPowerControllerInterface.java b/services/core/java/com/android/server/display/DisplayPowerControllerInterface.java
index 73edb97..5fbbcbd 100644
--- a/services/core/java/com/android/server/display/DisplayPowerControllerInterface.java
+++ b/services/core/java/com/android/server/display/DisplayPowerControllerInterface.java
@@ -29,7 +29,7 @@
  * An interface to manage the display's power state and brightness
  */
 public interface DisplayPowerControllerInterface {
-
+    int DEFAULT_USER_SERIAL = -1;
     /**
      * Notified when the display is changed.
      *
@@ -98,7 +98,17 @@
      * Set the screen brightness of the associated display
      * @param brightness The value to which the brightness is to be set
      */
-    void setBrightness(float brightness);
+    default void setBrightness(float brightness) {
+        setBrightness(brightness, DEFAULT_USER_SERIAL);
+    }
+
+    /**
+     * Set the screen brightness of the associated display
+     * @param brightness The value to which the brightness is to be set
+     * @param userSerial The user for which the brightness value is to be set. Use userSerial = -1,
+     * if brightness needs to be updated for the current user.
+     */
+    void setBrightness(float brightness, int userSerial);
 
     /**
      * Checks if the proximity sensor is available
diff --git a/services/core/java/com/android/server/display/PersistentDataStore.java b/services/core/java/com/android/server/display/PersistentDataStore.java
index 6d6ed72..2d7792d 100644
--- a/services/core/java/com/android/server/display/PersistentDataStore.java
+++ b/services/core/java/com/android/server/display/PersistentDataStore.java
@@ -133,6 +133,7 @@
 
     private static final String TAG_BRIGHTNESS_NITS_FOR_DEFAULT_DISPLAY =
             "brightness-nits-for-default-display";
+    public static final int DEFAULT_USER_ID = -1;
 
     // Remembered Wifi display devices.
     private ArrayList<WifiDisplay> mRememberedWifiDisplays = new ArrayList<WifiDisplay>();
@@ -294,7 +295,7 @@
         return false;
     }
 
-    public float getBrightness(DisplayDevice device) {
+    public float getBrightness(DisplayDevice device, int userSerial) {
         if (device == null || !device.hasStableUniqueId()) {
             return Float.NaN;
         }
@@ -302,10 +303,10 @@
         if (state == null) {
             return Float.NaN;
         }
-        return state.getBrightness();
+        return state.getBrightness(userSerial);
     }
 
-    public boolean setBrightness(DisplayDevice displayDevice, float brightness) {
+    public boolean setBrightness(DisplayDevice displayDevice, float brightness, int userSerial) {
         if (displayDevice == null || !displayDevice.hasStableUniqueId()) {
             return false;
         }
@@ -314,7 +315,7 @@
             return false;
         }
         final DisplayState state = getDisplayState(displayDeviceUniqueId, true);
-        if (state.setBrightness(brightness)) {
+        if (state.setBrightness(brightness, userSerial)) {
             setDirty();
             return true;
         }
@@ -611,6 +612,7 @@
             state.saveToXml(serializer);
             serializer.endTag(null, TAG_DISPLAY);
         }
+
         serializer.endTag(null, TAG_DISPLAY_STATES);
         serializer.startTag(null, TAG_STABLE_DEVICE_VALUES);
         mStableDeviceValues.saveToXml(serializer);
@@ -649,7 +651,8 @@
 
     private static final class DisplayState {
         private int mColorMode;
-        private float mBrightness = Float.NaN;
+
+        private SparseArray<Float> mPerUserBrightness = new SparseArray<>();
         private int mWidth;
         private int mHeight;
         private float mRefreshRate;
@@ -670,16 +673,25 @@
             return mColorMode;
         }
 
-        public boolean setBrightness(float brightness) {
-            if (brightness == mBrightness) {
+        public boolean setBrightness(float brightness, int userSerial) {
+            // Remove the default user brightness, before setting a new user-specific value.
+            // This is a one-time operation, required to restructure the config after user-specific
+            // brightness was introduced.
+            mPerUserBrightness.remove(DEFAULT_USER_ID);
+
+            if (getBrightness(userSerial) == brightness) {
                 return false;
             }
-            mBrightness = brightness;
+            mPerUserBrightness.set(userSerial, brightness);
             return true;
         }
 
-        public float getBrightness() {
-            return mBrightness;
+        public float getBrightness(int userSerial) {
+            float brightness = mPerUserBrightness.get(userSerial, Float.NaN);
+            if (Float.isNaN(brightness)) {
+                brightness = mPerUserBrightness.get(DEFAULT_USER_ID, Float.NaN);
+            }
+            return brightness;
         }
 
         public boolean setBrightnessConfiguration(BrightnessConfiguration configuration,
@@ -729,12 +741,7 @@
                         mColorMode = Integer.parseInt(value);
                         break;
                     case TAG_BRIGHTNESS_VALUE:
-                        String brightness = parser.nextText();
-                        try {
-                            mBrightness = Float.parseFloat(brightness);
-                        } catch (NumberFormatException e) {
-                            mBrightness = Float.NaN;
-                        }
+                        loadBrightnessFromXml(parser);
                         break;
                     case TAG_BRIGHTNESS_CONFIGURATIONS:
                         mDisplayBrightnessConfigurations.loadFromXml(parser);
@@ -760,11 +767,12 @@
             serializer.text(Integer.toString(mColorMode));
             serializer.endTag(null, TAG_COLOR_MODE);
 
-            serializer.startTag(null, TAG_BRIGHTNESS_VALUE);
-            if (!Float.isNaN(mBrightness)) {
-                serializer.text(Float.toString(mBrightness));
+            for (int i = 0; i < mPerUserBrightness.size(); i++) {
+                serializer.startTag(null, TAG_BRIGHTNESS_VALUE);
+                serializer.attributeInt(null, ATTR_USER_SERIAL, mPerUserBrightness.keyAt(i));
+                serializer.text(Float.toString(mPerUserBrightness.valueAt(i)));
+                serializer.endTag(null, TAG_BRIGHTNESS_VALUE);
             }
-            serializer.endTag(null, TAG_BRIGHTNESS_VALUE);
 
             serializer.startTag(null, TAG_BRIGHTNESS_CONFIGURATIONS);
             mDisplayBrightnessConfigurations.saveToXml(serializer);
@@ -785,12 +793,33 @@
 
         public void dump(final PrintWriter pw, final String prefix) {
             pw.println(prefix + "ColorMode=" + mColorMode);
-            pw.println(prefix + "BrightnessValue=" + mBrightness);
+            pw.println(prefix + "BrightnessValues: ");
+            for (int i = 0; i < mPerUserBrightness.size(); i++) {
+                pw.println("User: " + mPerUserBrightness.keyAt(i)
+                        + " Value: " + mPerUserBrightness.valueAt(i));
+            }
             pw.println(prefix + "DisplayBrightnessConfigurations: ");
             mDisplayBrightnessConfigurations.dump(pw, prefix);
             pw.println(prefix + "Resolution=" + mWidth + " " + mHeight);
             pw.println(prefix + "RefreshRate=" + mRefreshRate);
         }
+
+        private void loadBrightnessFromXml(TypedXmlPullParser parser)
+                throws IOException, XmlPullParserException {
+            int userSerial;
+            try {
+                userSerial = parser.getAttributeInt(null, ATTR_USER_SERIAL);
+            } catch (NumberFormatException | XmlPullParserException e) {
+                userSerial = DEFAULT_USER_ID;
+                Slog.e(TAG, "Failed to read user serial", e);
+            }
+            String brightness = parser.nextText();
+            try {
+                mPerUserBrightness.set(userSerial, Float.parseFloat(brightness));
+            } catch (NumberFormatException nfe) {
+                Slog.e(TAG, "Failed to read brightness", nfe);
+            }
+        }
     }
 
     private static final class StableDeviceValues {
diff --git a/services/core/java/com/android/server/display/brightness/DisplayBrightnessController.java b/services/core/java/com/android/server/display/brightness/DisplayBrightnessController.java
index 7574de8..2f52b70 100644
--- a/services/core/java/com/android/server/display/brightness/DisplayBrightnessController.java
+++ b/services/core/java/com/android/server/display/brightness/DisplayBrightnessController.java
@@ -38,6 +38,8 @@
  * display. Applies the chosen brightness.
  */
 public final class DisplayBrightnessController {
+    private static final int DEFAULT_USER_SERIAL = -1;
+
     // The ID of the display tied to this DisplayBrightnessController
     private final int mDisplayId;
 
@@ -274,8 +276,16 @@
      * Notifies the brightnessSetting to persist the supplied brightness value.
      */
     public void setBrightness(float brightnessValue) {
+        setBrightness(brightnessValue, DEFAULT_USER_SERIAL);
+    }
+
+    /**
+     * Notifies the brightnessSetting to persist the supplied brightness value for a user.
+     */
+    public void setBrightness(float brightnessValue, int userSerial) {
         // Update the setting, which will eventually call back into DPC to have us actually
         // update the display with the new value.
+        mBrightnessSetting.setUserSerial(userSerial);
         mBrightnessSetting.setBrightness(brightnessValue);
         if (mDisplayId == Display.DEFAULT_DISPLAY && mPersistBrightnessNitsForDefaultDisplay) {
             float nits = convertToNits(brightnessValue);
diff --git a/services/core/java/com/android/server/input/InputManagerService.java b/services/core/java/com/android/server/input/InputManagerService.java
index 9f3ab88..c7b80af 100644
--- a/services/core/java/com/android/server/input/InputManagerService.java
+++ b/services/core/java/com/android/server/input/InputManagerService.java
@@ -493,8 +493,6 @@
 
         // Add ourselves to the Watchdog monitors.
         Watchdog.getInstance().addMonitor(this);
-
-        mSettingsObserver.registerAndUpdate();
     }
 
     // TODO(BT) Pass in parameter for bluetooth system
@@ -505,6 +503,8 @@
 
         mDisplayManagerInternal = LocalServices.getService(DisplayManagerInternal.class);
 
+        mSettingsObserver.registerAndUpdate();
+
         synchronized (mLidSwitchLock) {
             mSystemReady = true;
 
diff --git a/services/core/java/com/android/server/input/InputSettingsObserver.java b/services/core/java/com/android/server/input/InputSettingsObserver.java
index 651063e..42591f4 100644
--- a/services/core/java/com/android/server/input/InputSettingsObserver.java
+++ b/services/core/java/com/android/server/input/InputSettingsObserver.java
@@ -115,35 +115,34 @@
         return setting != 0;
     }
 
-    private int getPointerSpeedValue(String settingName) {
-        int speed = Settings.System.getIntForUser(mContext.getContentResolver(),
-                settingName, InputSettings.DEFAULT_POINTER_SPEED, UserHandle.USER_CURRENT);
+    private int constrainPointerSpeedValue(int speed) {
         return Math.min(Math.max(speed, InputSettings.MIN_POINTER_SPEED),
                 InputSettings.MAX_POINTER_SPEED);
     }
 
     private void updateMousePointerSpeed() {
-        mNative.setPointerSpeed(getPointerSpeedValue(Settings.System.POINTER_SPEED));
+        int speed = Settings.System.getIntForUser(mContext.getContentResolver(),
+                Settings.System.POINTER_SPEED, InputSettings.DEFAULT_POINTER_SPEED,
+                UserHandle.USER_CURRENT);
+        mNative.setPointerSpeed(constrainPointerSpeedValue(speed));
     }
 
     private void updateTouchpadPointerSpeed() {
         mNative.setTouchpadPointerSpeed(
-                getPointerSpeedValue(Settings.System.TOUCHPAD_POINTER_SPEED));
+                constrainPointerSpeedValue(InputSettings.getTouchpadPointerSpeed(mContext)));
     }
 
     private void updateTouchpadNaturalScrollingEnabled() {
         mNative.setTouchpadNaturalScrollingEnabled(
-                getBoolean(Settings.System.TOUCHPAD_NATURAL_SCROLLING, true));
+                InputSettings.useTouchpadNaturalScrolling(mContext));
     }
 
     private void updateTouchpadTapToClickEnabled() {
-        mNative.setTouchpadTapToClickEnabled(
-                getBoolean(Settings.System.TOUCHPAD_TAP_TO_CLICK, true));
+        mNative.setTouchpadTapToClickEnabled(InputSettings.useTouchpadTapToClick(mContext));
     }
 
     private void updateTouchpadRightClickZoneEnabled() {
-        mNative.setTouchpadRightClickZoneEnabled(
-                getBoolean(Settings.System.TOUCHPAD_RIGHT_CLICK_ZONE, false));
+        mNative.setTouchpadRightClickZoneEnabled(InputSettings.useTouchpadRightClickZone(mContext));
     }
 
     private void updateShowTouches() {
diff --git a/services/core/java/com/android/server/media/MediaRoute2Provider.java b/services/core/java/com/android/server/media/MediaRoute2Provider.java
index c076c05..c59b733 100644
--- a/services/core/java/com/android/server/media/MediaRoute2Provider.java
+++ b/services/core/java/com/android/server/media/MediaRoute2Provider.java
@@ -31,6 +31,7 @@
 import java.util.ArrayList;
 import java.util.List;
 import java.util.Objects;
+import java.util.Set;
 
 abstract class MediaRoute2Provider {
     final ComponentName mComponentName;
@@ -56,7 +57,9 @@
     public abstract void requestCreateSession(long requestId, String packageName, String routeId,
             @Nullable Bundle sessionHints);
     public abstract void releaseSession(long requestId, String sessionId);
-    public abstract void updateDiscoveryPreference(RouteDiscoveryPreference discoveryPreference);
+
+    public abstract void updateDiscoveryPreference(
+            Set<String> activelyScanningPackages, RouteDiscoveryPreference discoveryPreference);
 
     public abstract void selectRoute(long requestId, String sessionId, String routeId);
     public abstract void deselectRoute(long requestId, String sessionId, String routeId);
diff --git a/services/core/java/com/android/server/media/MediaRoute2ProviderServiceProxy.java b/services/core/java/com/android/server/media/MediaRoute2ProviderServiceProxy.java
index 72b8436..3cf0786 100644
--- a/services/core/java/com/android/server/media/MediaRoute2ProviderServiceProxy.java
+++ b/services/core/java/com/android/server/media/MediaRoute2ProviderServiceProxy.java
@@ -49,6 +49,7 @@
 import java.util.Collections;
 import java.util.List;
 import java.util.Objects;
+import java.util.Set;
 
 /**
  * Maintains a connection to a particular {@link MediaRoute2ProviderService}.
@@ -61,6 +62,7 @@
     private final Context mContext;
     private final int mUserId;
     private final Handler mHandler;
+    private final boolean mIsSelfScanOnlyProvider;
 
     // Connection state
     private boolean mRunning;
@@ -70,14 +72,19 @@
 
     private boolean mIsManagerScanning;
     private RouteDiscoveryPreference mLastDiscoveryPreference = null;
+    private boolean mLastDiscoveryPreferenceIncludesThisPackage = false;
 
     @GuardedBy("mLock")
     final List<RoutingSessionInfo> mReleasingSessions = new ArrayList<>();
 
-    MediaRoute2ProviderServiceProxy(@NonNull Context context, @NonNull ComponentName componentName,
+    MediaRoute2ProviderServiceProxy(
+            @NonNull Context context,
+            @NonNull ComponentName componentName,
+            boolean isSelfScanOnlyProvider,
             int userId) {
         super(componentName);
         mContext = Objects.requireNonNull(context, "Context must not be null.");
+        mIsSelfScanOnlyProvider = isSelfScanOnlyProvider;
         mUserId = userId;
         mHandler = new Handler(Looper.myLooper());
     }
@@ -107,8 +114,11 @@
     }
 
     @Override
-    public void updateDiscoveryPreference(RouteDiscoveryPreference discoveryPreference) {
+    public void updateDiscoveryPreference(
+            Set<String> activelyScanningPackages, RouteDiscoveryPreference discoveryPreference) {
         mLastDiscoveryPreference = discoveryPreference;
+        mLastDiscoveryPreferenceIncludesThisPackage =
+                activelyScanningPackages.contains(mComponentName.getPackageName());
         if (mConnectionReady) {
             mActiveConnection.updateDiscoveryPreference(discoveryPreference);
         }
@@ -209,11 +219,15 @@
 
     private boolean shouldBind() {
         if (mRunning) {
-            // Bind when there is a discovery preference or an active route session.
-            return (mLastDiscoveryPreference != null
-                    && !mLastDiscoveryPreference.getPreferredFeatures().isEmpty())
-                    || !getSessionInfos().isEmpty()
-                    || mIsManagerScanning;
+            boolean shouldBind =
+                    mLastDiscoveryPreference != null
+                            && !mLastDiscoveryPreference.getPreferredFeatures().isEmpty();
+            if (mIsSelfScanOnlyProvider) {
+                shouldBind &= mLastDiscoveryPreferenceIncludesThisPackage;
+            }
+            shouldBind |= mIsManagerScanning;
+            shouldBind |= !getSessionInfos().isEmpty();
+            return shouldBind;
         }
         return false;
     }
@@ -301,7 +315,11 @@
         if (mActiveConnection == connection) {
             mConnectionReady = true;
             if (mLastDiscoveryPreference != null) {
-                updateDiscoveryPreference(mLastDiscoveryPreference);
+                updateDiscoveryPreference(
+                        mLastDiscoveryPreferenceIncludesThisPackage
+                                ? Set.of(mComponentName.getPackageName())
+                                : Set.of(),
+                        mLastDiscoveryPreference);
             }
         }
     }
diff --git a/services/core/java/com/android/server/media/MediaRoute2ProviderWatcher.java b/services/core/java/com/android/server/media/MediaRoute2ProviderWatcher.java
index 46bccaf..bd252e7 100644
--- a/services/core/java/com/android/server/media/MediaRoute2ProviderWatcher.java
+++ b/services/core/java/com/android/server/media/MediaRoute2ProviderWatcher.java
@@ -16,6 +16,8 @@
 
 package com.android.server.media;
 
+import static android.content.pm.PackageManager.GET_RESOLVED_FILTER;
+
 import android.annotation.NonNull;
 import android.content.BroadcastReceiver;
 import android.content.ComponentName;
@@ -34,6 +36,7 @@
 import java.io.PrintWriter;
 import java.util.ArrayList;
 import java.util.Collections;
+import java.util.Iterator;
 
 /**
  * Watches changes of packages, or scan them for finding media route providers.
@@ -41,8 +44,8 @@
 final class MediaRoute2ProviderWatcher {
     private static final String TAG = "MR2ProviderWatcher";
     private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
-    private static final PackageManager.ResolveInfoFlags RESOLVE_INFO_FLAGS_NONE =
-            PackageManager.ResolveInfoFlags.of(0);
+    private static final PackageManager.ResolveInfoFlags RESOLVE_INFO_FLAGS =
+            PackageManager.ResolveInfoFlags.of(GET_RESOLVED_FILTER);
 
     private final Context mContext;
     private final Callback mCallback;
@@ -118,16 +121,26 @@
         int targetIndex = 0;
         Intent intent = new Intent(MediaRoute2ProviderService.SERVICE_INTERFACE);
         for (ResolveInfo resolveInfo :
-                mPackageManager.queryIntentServicesAsUser(
-                        intent, RESOLVE_INFO_FLAGS_NONE, mUserId)) {
+                mPackageManager.queryIntentServicesAsUser(intent, RESOLVE_INFO_FLAGS, mUserId)) {
             ServiceInfo serviceInfo = resolveInfo.serviceInfo;
             if (serviceInfo != null) {
+                boolean isSelfScanOnlyProvider = false;
+                Iterator<String> categoriesIterator = resolveInfo.filter.categoriesIterator();
+                if (categoriesIterator != null) {
+                    while (categoriesIterator.hasNext()) {
+                        isSelfScanOnlyProvider |=
+                                MediaRoute2ProviderService.CATEGORY_SELF_SCAN_ONLY.equals(
+                                        categoriesIterator.next());
+                    }
+                }
                 int sourceIndex = findProvider(serviceInfo.packageName, serviceInfo.name);
                 if (sourceIndex < 0) {
                     MediaRoute2ProviderServiceProxy proxy =
-                            new MediaRoute2ProviderServiceProxy(mContext,
-                            new ComponentName(serviceInfo.packageName, serviceInfo.name),
-                            mUserId);
+                            new MediaRoute2ProviderServiceProxy(
+                                    mContext,
+                                    new ComponentName(serviceInfo.packageName, serviceInfo.name),
+                                    isSelfScanOnlyProvider,
+                                    mUserId);
                     proxy.start();
                     mProxies.add(targetIndex++, proxy);
                     mCallback.onAddProviderService(proxy);
diff --git a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
index b79991e..1059c19 100644
--- a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
+++ b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
@@ -1478,6 +1478,7 @@
         final ArrayList<RouterRecord> mRouterRecords = new ArrayList<>();
         final ArrayList<ManagerRecord> mManagerRecords = new ArrayList<>();
         RouteDiscoveryPreference mCompositeDiscoveryPreference = RouteDiscoveryPreference.EMPTY;
+        Set<String> mActivelyScanningPackages = Set.of();
         final UserHandler mHandler;
 
         UserRecord(int userId) {
@@ -1525,7 +1526,12 @@
                 pw.println(indent + "<no manager records>");
             }
 
-            mCompositeDiscoveryPreference.dump(pw, indent);
+            pw.println(indent + "Composite discovery preference:");
+            mCompositeDiscoveryPreference.dump(pw, indent + "  ");
+            pw.println(
+                    indent
+                            + "Packages actively scanning: "
+                            + String.join(", ", mActivelyScanningPackages));
 
             if (!mHandler.runWithScissors(() -> mHandler.dump(pw, indent), 1000)) {
                 pw.println(indent + "<could not dump handler state>");
@@ -1834,7 +1840,9 @@
         public void onAddProviderService(@NonNull MediaRoute2ProviderServiceProxy proxy) {
             proxy.setCallback(this);
             mRouteProviders.add(proxy);
-            proxy.updateDiscoveryPreference(mUserRecord.mCompositeDiscoveryPreference);
+            proxy.updateDiscoveryPreference(
+                    mUserRecord.mActivelyScanningPackages,
+                    mUserRecord.mCompositeDiscoveryPreference);
         }
 
         @Override
@@ -2341,8 +2349,8 @@
                     return;
                 }
                 notifySessionInfoChangedToRouters(getRouterRecords(true), sessionInfo);
-                notifySessionInfoChangedToRouters(getRouterRecords(false),
-                        mSystemProvider.getDefaultSessionInfo());
+                notifySessionInfoChangedToRouters(
+                        getRouterRecords(false), mSystemProvider.getDefaultSessionInfo());
                 return;
             }
 
@@ -2711,8 +2719,8 @@
             if (service == null) {
                 return;
             }
-            List<RouteDiscoveryPreference> discoveryPreferences = Collections.emptyList();
-            List<RouterRecord> routerRecords = getRouterRecords();
+            List<RouterRecord> activeRouterRecords = Collections.emptyList();
+            List<RouterRecord> allRouterRecords = getRouterRecords();
             List<ManagerRecord> managerRecords = getManagerRecords();
 
             boolean isManagerScanning = false;
@@ -2723,15 +2731,16 @@
                                 <= sPackageImportanceForScanning);
 
                 if (isManagerScanning) {
-                    discoveryPreferences = routerRecords.stream()
-                            .map(record -> record.mDiscoveryPreference)
-                            .collect(Collectors.toList());
+                    activeRouterRecords = allRouterRecords;
                 } else {
-                    discoveryPreferences = routerRecords.stream().filter(record ->
-                            service.mActivityManager.getPackageImportance(record.mPackageName)
-                                    <= sPackageImportanceForScanning)
-                            .map(record -> record.mDiscoveryPreference)
-                            .collect(Collectors.toList());
+                    activeRouterRecords =
+                            allRouterRecords.stream()
+                                    .filter(
+                                            record ->
+                                                    service.mActivityManager.getPackageImportance(
+                                                                    record.mPackageName)
+                                                            <= sPackageImportanceForScanning)
+                                    .collect(Collectors.toList());
                 }
             }
 
@@ -2748,22 +2757,30 @@
             // to query route providers once to obtain all of the routes of interest, which
             // can be subsequently filtered for the individual discovery preferences.
             Set<String> preferredFeatures = new HashSet<>();
+            Set<String> activelyScanningPackages = new HashSet<>();
             boolean activeScan = false;
-            for (RouteDiscoveryPreference preference : discoveryPreferences) {
+            for (RouterRecord activeRouterRecord : activeRouterRecords) {
+                RouteDiscoveryPreference preference = activeRouterRecord.mDiscoveryPreference;
                 preferredFeatures.addAll(preference.getPreferredFeatures());
-                activeScan |= preference.shouldPerformActiveScan();
+                if (preference.shouldPerformActiveScan()) {
+                    activeScan = true;
+                    activelyScanningPackages.add(activeRouterRecord.mPackageName);
+                }
             }
             RouteDiscoveryPreference newPreference = new RouteDiscoveryPreference.Builder(
                     List.copyOf(preferredFeatures), activeScan || isManagerScanning).build();
 
             synchronized (service.mLock) {
-                if (newPreference.equals(mUserRecord.mCompositeDiscoveryPreference)) {
+                if (newPreference.equals(mUserRecord.mCompositeDiscoveryPreference)
+                        && activelyScanningPackages.equals(mUserRecord.mActivelyScanningPackages)) {
                     return;
                 }
                 mUserRecord.mCompositeDiscoveryPreference = newPreference;
+                mUserRecord.mActivelyScanningPackages = activelyScanningPackages;
             }
             for (MediaRoute2Provider provider : mRouteProviders) {
-                provider.updateDiscoveryPreference(mUserRecord.mCompositeDiscoveryPreference);
+                provider.updateDiscoveryPreference(
+                        activelyScanningPackages, mUserRecord.mCompositeDiscoveryPreference);
             }
         }
 
diff --git a/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java b/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java
index 426bc5e..f4e6abd 100644
--- a/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java
+++ b/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java
@@ -42,6 +42,7 @@
 
 import java.util.List;
 import java.util.Objects;
+import java.util.Set;
 
 /**
  * Provides routes for local playbacks such as phone speaker, wired headset, or Bluetooth speakers.
@@ -196,7 +197,8 @@
     }
 
     @Override
-    public void updateDiscoveryPreference(RouteDiscoveryPreference discoveryPreference) {
+    public void updateDiscoveryPreference(
+            Set<String> activelyScanningPackages, RouteDiscoveryPreference discoveryPreference) {
         // Do nothing
     }
 
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index d5009f6..b1a289f 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -1095,6 +1095,8 @@
 
         final DreamManagerInternal dreamManagerInternal = getDreamManagerInternal();
         if (dreamManagerInternal == null || !dreamManagerInternal.canStartDreaming(isScreenOn)) {
+            Slog.d(TAG, "Can't start dreaming when attempting to dream from short power"
+                    + " press (isScreenOn=" + isScreenOn + ")");
             noDreamAction.run();
             return;
         }
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 3db0315..2b2100e 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -5630,11 +5630,18 @@
             setClientVisible(visible);
         }
 
+        final DisplayContent displayContent = getDisplayContent();
         if (!visible) {
             mImeInsetsFrozenUntilStartInput = true;
+            if (usingShellTransitions) {
+                final WindowState wallpaperTarget =
+                        displayContent.mWallpaperController.getWallpaperTarget();
+                if (wallpaperTarget != null && wallpaperTarget.mActivityRecord == this) {
+                    displayContent.mWallpaperController.hideWallpapers(wallpaperTarget);
+                }
+            }
         }
 
-        final DisplayContent displayContent = getDisplayContent();
         if (!displayContent.mClosingApps.contains(this)
                 && !displayContent.mOpeningApps.contains(this)
                 && !fromTransition) {
diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java
index 3e3eb57..53a4752 100644
--- a/services/core/java/com/android/server/wm/ActivityStarter.java
+++ b/services/core/java/com/android/server/wm/ActivityStarter.java
@@ -1821,7 +1821,8 @@
         // If Activity's launching into PiP, move the mStartActivity immediately to pinned mode.
         // Note that mStartActivity and source should be in the same Task at this point.
         if (mOptions != null && mOptions.isLaunchIntoPip()
-                && sourceRecord != null && sourceRecord.getTask() == mStartActivity.getTask()) {
+                && sourceRecord != null && sourceRecord.getTask() == mStartActivity.getTask()
+                && balCode != BAL_BLOCK) {
             mRootWindowContainer.moveActivityToPinnedRootTask(mStartActivity,
                     sourceRecord, "launch-into-pip");
         }
@@ -2925,15 +2926,6 @@
             }
         }
 
-        // If the matching task is already in the adjacent task of the launch target. Adjust to use
-        // the adjacent task as its launch target. So the existing task will be launched into the
-        // closer one and won't be reparent redundantly.
-        final Task adjacentTargetTask = mTargetRootTask.getAdjacentTask();
-        if (adjacentTargetTask != null && intentActivity.isDescendantOf(adjacentTargetTask)
-                && intentTask.isOnTop()) {
-            mTargetRootTask = adjacentTargetTask;
-        }
-
         // If the target task is not in the front, then we need to bring it to the front...
         // except...  well, with SINGLE_TASK_LAUNCH it's not entirely clear. We'd like to have
         // the same behavior as if a new instance was being started, which means not bringing it
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index 4d4ffba..a650d77 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -3560,10 +3560,10 @@
                 mRootWindowContainer.forAllDisplays(displayContent -> {
                     mKeyguardController.keyguardGoingAway(displayContent.getDisplayId(), flags);
                 });
-                WallpaperManagerInternal wallpaperManagerInternal = getWallpaperManagerInternal();
-                if (wallpaperManagerInternal != null) {
-                    wallpaperManagerInternal.onKeyguardGoingAway();
-                }
+            }
+            WallpaperManagerInternal wallpaperManagerInternal = getWallpaperManagerInternal();
+            if (wallpaperManagerInternal != null) {
+                wallpaperManagerInternal.onKeyguardGoingAway();
             }
         } finally {
             Binder.restoreCallingIdentity(token);
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index e44d279..6aec969 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -6511,6 +6511,22 @@
                 .getKeyguardController().isDisplayOccluded(mDisplayId);
     }
 
+    /**
+     * @return the task that is occluding the keyguard
+     */
+    @Nullable
+    Task getTaskOccludingKeyguard() {
+        final KeyguardController keyguardController = mRootWindowContainer.mTaskSupervisor
+                .getKeyguardController();
+        if (keyguardController.getTopOccludingActivity(mDisplayId) != null) {
+            return keyguardController.getTopOccludingActivity(mDisplayId).getRootTask();
+        }
+        if (keyguardController.getDismissKeyguardActivity(mDisplayId) != null) {
+            return keyguardController.getDismissKeyguardActivity(mDisplayId).getRootTask();
+        }
+        return null;
+    }
+
     @VisibleForTesting
     void removeAllTasks() {
         forAllTasks((t) -> { t.getRootTask().removeChild(t, "removeAllTasks"); });
diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java
index 812d2a5..8dcb042 100644
--- a/services/core/java/com/android/server/wm/DisplayPolicy.java
+++ b/services/core/java/com/android/server/wm/DisplayPolicy.java
@@ -1859,6 +1859,9 @@
              */
             final Rect mConfigFrame = new Rect();
 
+            /** The count of insets sources when calculating this info. */
+            int mLastInsetsSourceCount;
+
             private boolean mNeedUpdate = true;
 
             void update(DisplayContent dc, int rotation, int w, int h) {
@@ -1880,6 +1883,7 @@
                 mNonDecorFrame.inset(mNonDecorInsets);
                 mConfigFrame.set(displayFrame);
                 mConfigFrame.inset(mConfigInsets);
+                mLastInsetsSourceCount = dc.getDisplayPolicy().mInsetsSourceWindowsExceptIme.size();
                 mNeedUpdate = false;
             }
 
@@ -1888,6 +1892,7 @@
                 mConfigInsets.set(other.mConfigInsets);
                 mNonDecorFrame.set(other.mNonDecorFrame);
                 mConfigFrame.set(other.mConfigFrame);
+                mLastInsetsSourceCount = other.mLastInsetsSourceCount;
                 mNeedUpdate = false;
             }
 
@@ -1986,6 +1991,19 @@
         newInfo.update(mDisplayContent, rotation, dw, dh);
         final DecorInsets.Info currentInfo = getDecorInsetsInfo(rotation, dw, dh);
         if (newInfo.mConfigFrame.equals(currentInfo.mConfigFrame)) {
+            // Even if the config frame is not changed in current rotation, it may change the
+            // insets in other rotations if the source count is changed.
+            if (newInfo.mLastInsetsSourceCount != currentInfo.mLastInsetsSourceCount) {
+                for (int i = mDecorInsets.mInfoForRotation.length - 1; i >= 0; i--) {
+                    if (i != rotation) {
+                        final boolean flipSize = (i + rotation) % 2 == 1;
+                        final int w = flipSize ? dh : dw;
+                        final int h = flipSize ? dw : dh;
+                        mDecorInsets.mInfoForRotation[i].update(mDisplayContent, i, w, h);
+                    }
+                }
+                mDecorInsets.mInfoForRotation[rotation].set(newInfo);
+            }
             return false;
         }
         if (mCachedDecorInsets != null && !mCachedDecorInsets.canPreserve()
diff --git a/services/core/java/com/android/server/wm/KeyguardController.java b/services/core/java/com/android/server/wm/KeyguardController.java
index 5f6d660..99878a3 100644
--- a/services/core/java/com/android/server/wm/KeyguardController.java
+++ b/services/core/java/com/android/server/wm/KeyguardController.java
@@ -17,7 +17,6 @@
 package com.android.server.wm;
 
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_DREAM;
-import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
 import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER;
 import static android.view.Display.DEFAULT_DISPLAY;
 import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY;
@@ -411,19 +410,20 @@
         if (waitAppTransition) {
             mService.deferWindowLayout();
             try {
-                mRootWindowContainer.getDefaultDisplay()
-                        .requestTransitionAndLegacyPrepare(
-                                isDisplayOccluded(DEFAULT_DISPLAY)
-                                        ? TRANSIT_KEYGUARD_OCCLUDE
-                                        : TRANSIT_KEYGUARD_UNOCCLUDE, 0 /* flags */);
+                if (isDisplayOccluded(DEFAULT_DISPLAY)) {
+                    mRootWindowContainer.getDefaultDisplay().requestTransitionAndLegacyPrepare(
+                            TRANSIT_KEYGUARD_OCCLUDE,
+                            topActivity != null ? topActivity.getRootTask() : null);
+                } else {
+                    mRootWindowContainer.getDefaultDisplay().requestTransitionAndLegacyPrepare(
+                            TRANSIT_KEYGUARD_UNOCCLUDE, 0 /* flags */);
+                }
                 updateKeyguardSleepToken(DEFAULT_DISPLAY);
                 mWindowManager.executeAppTransition();
             } finally {
                 mService.continueWindowLayout();
             }
         }
-        dismissMultiWindowModeForTaskIfNeeded(displayId, topActivity != null
-                ? topActivity.getRootTask() : null);
     }
 
     /**
@@ -473,6 +473,14 @@
         return getDisplayState(displayId).mOccluded;
     }
 
+    ActivityRecord getTopOccludingActivity(int displayId) {
+        return getDisplayState(displayId).mTopOccludesActivity;
+    }
+
+    ActivityRecord getDismissKeyguardActivity(int displayId) {
+        return getDisplayState(displayId).mDismissingKeyguardActivity;
+    }
+
     /**
      * @return true if Keyguard can be currently dismissed without entering credentials.
      */
@@ -488,22 +496,6 @@
         return getDisplayState(DEFAULT_DISPLAY).mShowingDream;
     }
 
-    private void dismissMultiWindowModeForTaskIfNeeded(int displayId,
-            @Nullable Task currentTaskControllingOcclusion) {
-        // TODO(b/113840485): Handle docked stack for individual display.
-        if (!getDisplayState(displayId).mKeyguardShowing || !isDisplayOccluded(DEFAULT_DISPLAY)) {
-            return;
-        }
-
-        // Dismiss freeform windowing mode
-        if (currentTaskControllingOcclusion == null) {
-            return;
-        }
-        if (currentTaskControllingOcclusion.inFreeformWindowingMode()) {
-            currentTaskControllingOcclusion.setWindowingMode(WINDOWING_MODE_FULLSCREEN);
-        }
-    }
-
     private void updateKeyguardSleepToken() {
         for (int displayNdx = mRootWindowContainer.getChildCount() - 1;
              displayNdx >= 0; displayNdx--) {
diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java
index 5369159..7e83959 100644
--- a/services/core/java/com/android/server/wm/RootWindowContainer.java
+++ b/services/core/java/com/android/server/wm/RootWindowContainer.java
@@ -2376,6 +2376,7 @@
             if (!displayShouldSleep && display.mTransitionController.isShellTransitionsEnabled()
                     && !display.mTransitionController.isCollecting()) {
                 int transit = TRANSIT_NONE;
+                Task startTask = null;
                 if (!display.getDisplayPolicy().isAwake()) {
                     // Note that currently this only happens on default display because non-default
                     // display is always awake.
@@ -2383,12 +2384,12 @@
                 } else if (display.isKeyguardOccluded()) {
                     // The display was awake so this is resuming activity for occluding keyguard.
                     transit = WindowManager.TRANSIT_KEYGUARD_OCCLUDE;
+                    startTask = display.getTaskOccludingKeyguard();
                 }
                 if (transit != TRANSIT_NONE) {
                     display.mTransitionController.requestStartTransition(
                             display.mTransitionController.createTransition(transit),
-                            null /* startTask */, null /* remoteTransition */,
-                            null /* displayChange */);
+                            startTask, null /* remoteTransition */, null /* displayChange */);
                 }
             }
             // Set the sleeping state of the root tasks on the display.
diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java
index d5a7ff5..b938b9e 100644
--- a/services/core/java/com/android/server/wm/Transition.java
+++ b/services/core/java/com/android/server/wm/Transition.java
@@ -2249,8 +2249,10 @@
             while (leashReference.getParent() != ancestor) {
                 leashReference = leashReference.getParent();
             }
+
             final SurfaceControl rootLeash = leashReference.makeAnimationLeash().setName(
                     "Transition Root: " + leashReference.getName()).build();
+            rootLeash.setUnreleasedWarningCallSite("Transition.calculateTransitionRoots");
             startT.setLayer(rootLeash, leashReference.getLastLayer());
             outInfo.addRootLeash(endDisplayId, rootLeash,
                     ancestor.getBounds().left, ancestor.getBounds().top);
diff --git a/services/core/java/com/android/server/wm/WallpaperController.java b/services/core/java/com/android/server/wm/WallpaperController.java
index edafe06..20ce98c 100644
--- a/services/core/java/com/android/server/wm/WallpaperController.java
+++ b/services/core/java/com/android/server/wm/WallpaperController.java
@@ -146,11 +146,10 @@
             }
         } else {
             final ActivityRecord ar = w.mActivityRecord;
-            final TransitionController tc = w.mTransitionController;
             // The animating window can still be visible on screen if it is in transition, so we
             // should check whether this window can be wallpaper target even when visibleRequested
             // is false.
-            if (ar != null && !ar.isVisibleRequested() && !tc.inTransition(ar)) {
+            if (ar != null && !ar.isVisibleRequested() && !ar.isVisible()) {
                 // An activity that is not going to remain visible shouldn't be the target.
                 return false;
             }
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 6020711..7464045 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -11113,7 +11113,7 @@
                 || hasCallingOrSelfPermission(permission.INTERACT_ACROSS_USERS);
     }
 
-    private boolean canUserUseLockTaskLocked(int userId) {
+    private boolean canDPCManagedUserUseLockTaskLocked(int userId) {
         if (isUserAffiliatedWithDeviceLocked(userId)) {
             return true;
         }
@@ -11122,19 +11122,16 @@
         if (mOwners.hasDeviceOwner()) {
             return false;
         }
-
-        if (!isPermissionCheckFlagEnabled() && !isPolicyEngineForFinanceFlagEnabled()) {
-            final ComponentName profileOwner = getProfileOwnerAsUser(userId);
-            if (profileOwner == null) {
-                return false;
-            }
+        
+        final ComponentName profileOwner = getProfileOwnerAsUser(userId);
+        if (profileOwner == null) {
+            return false;
         }
-
         // Managed profiles are not allowed to use lock task
         if (isManagedProfile(userId)) {
             return false;
         }
-
+        
         return true;
     }
     private void enforceCanQueryLockTaskLocked(ComponentName who, String callerPackageName) {
@@ -11142,7 +11139,8 @@
         final int userId = caller.getUserId();
 
         enforceCanQuery(MANAGE_DEVICE_POLICY_LOCK_TASK, caller.getPackageName(), userId);
-        if (!canUserUseLockTaskLocked(userId)) {
+        if ((isDeviceOwner(caller) || isProfileOwner(caller))
+                && !canDPCManagedUserUseLockTaskLocked(userId)) {
             throw new SecurityException("User " + userId + " is not allowed to use lock task");
         }
     }
@@ -11158,7 +11156,8 @@
                 caller.getPackageName(),
                 userId
         );
-        if (!canUserUseLockTaskLocked(userId)) {
+        if ((isDeviceOwner(caller) || isProfileOwner(caller))
+                && !canDPCManagedUserUseLockTaskLocked(userId)) {
             throw new SecurityException("User " + userId + " is not allowed to use lock task");
         }
         return enforcingAdmin;
@@ -11169,7 +11168,7 @@
                 || isDefaultDeviceOwner(caller) || isFinancedDeviceOwner(caller));
 
         final int userId =  caller.getUserId();
-        if (!canUserUseLockTaskLocked(userId)) {
+        if (!canDPCManagedUserUseLockTaskLocked(userId)) {
             throw new SecurityException("User " + userId + " is not allowed to use lock task");
         }
     }
@@ -15101,7 +15100,7 @@
             final List<UserInfo> userInfos = mUserManager.getAliveUsers();
             for (int i = userInfos.size() - 1; i >= 0; i--) {
                 int userId = userInfos.get(i).id;
-                if (canUserUseLockTaskLocked(userId)) {
+                if (canDPCManagedUserUseLockTaskLocked(userId)) {
                     continue;
                 }
 
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java b/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java
index cda5456..770f04a 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java
@@ -68,6 +68,7 @@
 import static com.android.server.am.ProcessList.VISIBLE_APP_ADJ;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotEquals;
 import static org.junit.Assert.assertTrue;
 import static org.mockito.AdditionalAnswers.answer;
@@ -2489,6 +2490,28 @@
         assertProcStates(app2, false, PROCESS_STATE_SERVICE, SERVICE_ADJ, "started-services");
     }
 
+    @SuppressWarnings("GuardedBy")
+    @Test
+    public void testUpdateOomAdj_DoOne_AboveClient_SameProcess() {
+        ProcessRecord app = spy(makeDefaultProcessRecord(MOCKAPP_PID, MOCKAPP_UID,
+                MOCKAPP_PROCESSNAME, MOCKAPP_PACKAGENAME, true));
+        doReturn(PROCESS_STATE_TOP).when(sService.mAtmInternal).getTopProcessState();
+        doReturn(app).when(sService).getTopApp();
+        sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
+        sService.mOomAdjuster.updateOomAdjLocked(app, OOM_ADJ_REASON_NONE);
+
+        assertEquals(FOREGROUND_APP_ADJ, app.mState.getSetAdj());
+
+        // Simulate binding to a service in the same process using BIND_ABOVE_CLIENT and
+        // verify that its OOM adjustment level is unaffected.
+        bindService(app, app, null, Context.BIND_ABOVE_CLIENT, mock(IBinder.class));
+        app.mServices.updateHasAboveClientLocked();
+        assertFalse(app.mServices.hasAboveClient());
+
+        sService.mOomAdjuster.updateOomAdjLocked(app, OOM_ADJ_REASON_NONE);
+        assertEquals(FOREGROUND_APP_ADJ, app.mState.getSetAdj());
+    }
+
     private ProcessRecord makeDefaultProcessRecord(int pid, int uid, String processName,
             String packageName, boolean hasShownUi) {
         long now = SystemClock.uptimeMillis();
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/log/BiometricContextProviderTest.java b/services/tests/servicestests/src/com/android/server/biometrics/log/BiometricContextProviderTest.java
index fb3a5f6..a442303 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/log/BiometricContextProviderTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/log/BiometricContextProviderTest.java
@@ -316,13 +316,13 @@
         assertThat(aidlContext.isAod).isEqualTo(false);
         assertThat(aidlContext.isCrypto).isEqualTo(false);
 
-        context = mProvider.updateContext(mOpContext, false /* crypto */);
+        context = mProvider.updateContext(mOpContext, true /* crypto */);
         aidlContext = context.toAidlContext();
         assertThat(context).isSameInstanceAs(mOpContext);
         assertThat(aidlContext.id).isEqualTo(0);
         assertThat(aidlContext.reason).isEqualTo(OperationReason.UNKNOWN);
         assertThat(aidlContext.isAod).isEqualTo(false);
-        assertThat(aidlContext.isCrypto).isEqualTo(false);
+        assertThat(aidlContext.isCrypto).isEqualTo(true);
     }
 
     @Test
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/log/OperationContextExtTest.java b/services/tests/servicestests/src/com/android/server/biometrics/log/OperationContextExtTest.java
index 5cf5960..32284fd 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/log/OperationContextExtTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/log/OperationContextExtTest.java
@@ -98,7 +98,7 @@
         for (Map.Entry<Integer, Integer> entry : map.entrySet()) {
             final OperationContextExt context = new OperationContextExt(newAidlContext(), true);
             when(mBiometricContext.getDisplayState()).thenReturn(entry.getKey());
-            assertThat(context.update(mBiometricContext).getDisplayState())
+            assertThat(context.update(mBiometricContext, context.isCrypto()).getDisplayState())
                     .isEqualTo(entry.getValue());
         }
     }
@@ -139,7 +139,7 @@
         final OperationContextExt context = new OperationContextExt(newAidlContext(),
                 sessionType == OperationReason.BIOMETRIC_PROMPT);
 
-        assertThat(context.update(mBiometricContext)).isSameInstanceAs(context);
+        assertThat(context.update(mBiometricContext, context.isCrypto())).isSameInstanceAs(context);
 
         if (sessionInfo != null) {
             assertThat(context.getId()).isEqualTo(sessionInfo.getId());
diff --git a/services/tests/servicestests/src/com/android/server/contentcapture/ContentCaptureManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/contentcapture/ContentCaptureManagerServiceTest.java
new file mode 100644
index 0000000..c872a11
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/contentcapture/ContentCaptureManagerServiceTest.java
@@ -0,0 +1,114 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.contentcapture;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.when;
+
+import android.content.ContentCaptureOptions;
+import android.content.Context;
+import android.content.pm.UserInfo;
+
+import androidx.test.core.app.ApplicationProvider;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
+import com.android.server.LocalServices;
+import com.android.server.pm.UserManagerInternal;
+
+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;
+
+/**
+ * Test for {@link ContentCaptureManagerService}.
+ *
+ * <p>Run with: {@code atest
+ * FrameworksServicesTests:com.android.server.contentcapture.ContentCaptureManagerServiceTest}
+ */
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+@SuppressWarnings("GuardedBy") // Service not really running, no need to expose locks
+public class ContentCaptureManagerServiceTest {
+
+    private static final int USER_ID = 1234;
+
+    private static final String PACKAGE_NAME = "com.test.package";
+
+    private static final Context sContext = ApplicationProvider.getApplicationContext();
+
+    @Rule public final MockitoRule mMockitoRule = MockitoJUnit.rule();
+
+    @Mock private UserManagerInternal mMockUserManagerInternal;
+
+    private ContentCaptureManagerService mContentCaptureManagerService;
+
+    @Before
+    public void setup() {
+        when(mMockUserManagerInternal.getUserInfos()).thenReturn(new UserInfo[0]);
+        LocalServices.removeServiceForTest(UserManagerInternal.class);
+        LocalServices.addService(UserManagerInternal.class, mMockUserManagerInternal);
+        mContentCaptureManagerService = new ContentCaptureManagerService(sContext);
+    }
+
+    @Test
+    public void getOptions_notAllowlisted() {
+        ContentCaptureOptions actual =
+                mContentCaptureManagerService.mGlobalContentCaptureOptions.getOptions(
+                        USER_ID, PACKAGE_NAME);
+
+        assertThat(actual).isNull();
+    }
+
+    @Test
+    public void getOptions_allowlisted_contentCaptureReceiver() {
+        mContentCaptureManagerService.mGlobalContentCaptureOptions.setWhitelist(
+                USER_ID, ImmutableList.of(PACKAGE_NAME), /* components= */ null);
+
+        ContentCaptureOptions actual =
+                mContentCaptureManagerService.mGlobalContentCaptureOptions.getOptions(
+                        USER_ID, PACKAGE_NAME);
+
+        assertThat(actual).isNotNull();
+        assertThat(actual.enableReceiver).isTrue();
+        assertThat(actual.contentProtectionOptions.enableReceiver).isFalse();
+        assertThat(actual.whitelistedComponents).isNull();
+    }
+
+    @Test
+    public void getOptions_allowlisted_bothReceivers() {
+        mContentCaptureManagerService.mDevCfgEnableContentProtectionReceiver = true;
+        mContentCaptureManagerService.mGlobalContentCaptureOptions.setWhitelist(
+                USER_ID, ImmutableList.of(PACKAGE_NAME), /* components= */ null);
+
+        ContentCaptureOptions actual =
+                mContentCaptureManagerService.mGlobalContentCaptureOptions.getOptions(
+                        USER_ID, PACKAGE_NAME);
+
+        assertThat(actual).isNotNull();
+        assertThat(actual.enableReceiver).isTrue();
+        assertThat(actual.contentProtectionOptions.enableReceiver).isTrue();
+        assertThat(actual.whitelistedComponents).isNull();
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/contentcapture/OWNERS b/services/tests/servicestests/src/com/android/server/contentcapture/OWNERS
new file mode 100644
index 0000000..b3583a7
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/contentcapture/OWNERS
@@ -0,0 +1,4 @@
+# Bug component: 544200
+
+include /core/java/android/view/contentcapture/OWNERS
+
diff --git a/services/tests/servicestests/src/com/android/server/display/AutomaticBrightnessControllerTest.java b/services/tests/servicestests/src/com/android/server/display/AutomaticBrightnessControllerTest.java
index 17fa33c..a6acd60 100644
--- a/services/tests/servicestests/src/com/android/server/display/AutomaticBrightnessControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/AutomaticBrightnessControllerTest.java
@@ -476,6 +476,42 @@
     }
 
     @Test
+    public void testSwitchBetweenModesNoUserInteractions() throws Exception {
+        ArgumentCaptor<SensorEventListener> listenerCaptor =
+                ArgumentCaptor.forClass(SensorEventListener.class);
+        verify(mSensorManager).registerListener(listenerCaptor.capture(), eq(mLightSensor),
+                eq(INITIAL_LIGHT_SENSOR_RATE * 1000), any(Handler.class));
+        SensorEventListener listener = listenerCaptor.getValue();
+
+        // Sensor reads 123 lux,
+        listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 123));
+        when(mBrightnessMappingStrategy.getShortTermModelTimeout()).thenReturn(2000L);
+        when(mBrightnessMappingStrategy.getUserBrightness()).thenReturn(-1.0f);
+        when(mBrightnessMappingStrategy.getUserLux()).thenReturn(-1.0f);
+
+        // No user brightness interaction.
+
+        mController.switchToIdleMode();
+        when(mIdleBrightnessMappingStrategy.isForIdleMode()).thenReturn(true);
+        when(mIdleBrightnessMappingStrategy.getUserBrightness()).thenReturn(-1.0f);
+        when(mIdleBrightnessMappingStrategy.getUserLux()).thenReturn(-1.0f);
+
+        // Sensor reads 1000 lux,
+        listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 1000));
+        // Do not fast-forward time.
+        mTestLooper.dispatchAll();
+
+        mController.switchToInteractiveScreenBrightnessMode();
+        // Do not fast-forward time
+        mTestLooper.dispatchAll();
+
+        // Ensure that there are no data points added, since the user has never adjusted the
+        // brightness
+        verify(mBrightnessMappingStrategy, times(0))
+                .addUserDataPoint(anyFloat(), anyFloat());
+    }
+
+    @Test
     public void testSwitchToIdleMappingStrategy() throws Exception {
         ArgumentCaptor<SensorEventListener> listenerCaptor =
                 ArgumentCaptor.forClass(SensorEventListener.class);
diff --git a/services/tests/servicestests/src/com/android/server/display/PersistentDataStoreTest.java b/services/tests/servicestests/src/com/android/server/display/PersistentDataStoreTest.java
index 817b245..642f54c 100644
--- a/services/tests/servicestests/src/com/android/server/display/PersistentDataStoreTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/PersistentDataStoreTest.java
@@ -60,6 +60,114 @@
     }
 
     @Test
+    public void testLoadBrightness() {
+        final String uniqueDisplayId = "test:123";
+        final DisplayDevice testDisplayDevice = new DisplayDevice(
+                null, null, uniqueDisplayId, null) {
+            @Override
+            public boolean hasStableUniqueId() {
+                return true;
+            }
+
+            @Override
+            public DisplayDeviceInfo getDisplayDeviceInfoLocked() {
+                return null;
+            }
+        };
+
+        String contents = "<?xml version='1.0' encoding='utf-8' standalone='yes' ?>\n"
+                + "<display-manager-state>\n"
+                + "  <display-states>\n"
+                + "    <display unique-id=\"test:123\">\n"
+                + "      <brightness-value user-serial=\"1\">0.1</brightness-value>\n"
+                + "      <brightness-value user-serial=\"2\">0.2</brightness-value>\n"
+                + "    </display>\n"
+                + "  </display-states>\n"
+                + "</display-manager-state>\n";
+
+        InputStream is = new ByteArrayInputStream(contents.getBytes(StandardCharsets.UTF_8));
+        mInjector.setReadStream(is);
+        mDataStore.loadIfNeeded();
+
+        float brightness = mDataStore.getBrightness(testDisplayDevice, 1);
+        assertEquals(0.1, brightness, 0.01);
+
+        brightness = mDataStore.getBrightness(testDisplayDevice, 2);
+        assertEquals(0.2, brightness, 0.01);
+    }
+
+    @Test
+    public void testSetBrightness_brightnessTagWithNoUserId_updatesToBrightnessTagWithUserId() {
+        final String uniqueDisplayId = "test:123";
+        final DisplayDevice testDisplayDevice =
+                new DisplayDevice(null, null, uniqueDisplayId, null) {
+            @Override
+            public boolean hasStableUniqueId() {
+                return true;
+            }
+
+            @Override
+            public DisplayDeviceInfo getDisplayDeviceInfoLocked() {
+                return null;
+            }
+        };
+
+        String contents = "<?xml version='1.0' encoding='utf-8' standalone='yes' ?>\n"
+                + "<display-manager-state>\n"
+                + "  <display-states>\n"
+                + "    <color-mode>0</color-mode>\n"
+                + "    <display unique-id=\"test:123\">\n"
+                + "      <brightness-value>0.5</brightness-value>\n"
+                + "    </display>\n"
+                + "  </display-states>\n"
+                + "</display-manager-state>\n";
+
+        InputStream is = new ByteArrayInputStream(contents.getBytes(StandardCharsets.UTF_8));
+        mInjector.setReadStream(is);
+        mDataStore.loadIfNeeded();
+
+        float user1Brightness = mDataStore.getBrightness(testDisplayDevice, 1 /* userSerial */);
+        float user2Brightness = mDataStore.getBrightness(testDisplayDevice, 2 /* userSerial */);
+        assertEquals(0.5, user1Brightness, 0.01);
+        assertEquals(0.5, user2Brightness, 0.01);
+
+        // Override the value for user 2. Default user must have been removed.
+        mDataStore.setBrightness(testDisplayDevice, 0.2f, 2 /* userSerial */  /* brightness*/);
+
+        user1Brightness = mDataStore.getBrightness(testDisplayDevice, 1 /* userSerial */);
+        user2Brightness = mDataStore.getBrightness(testDisplayDevice, 2 /* userSerial */);
+        assertTrue(Float.isNaN(user1Brightness));
+        assertEquals(0.2f, user2Brightness, 0.01);
+
+        // Override the value for user 1. User-specific brightness values should co-exist.
+        mDataStore.setBrightness(testDisplayDevice, 0.1f, 1 /* userSerial */  /* brightness*/);
+        user1Brightness = mDataStore.getBrightness(testDisplayDevice, 1 /* userSerial */);
+        user2Brightness = mDataStore.getBrightness(testDisplayDevice, 2 /* userSerial */);
+        assertEquals(0.1f, user1Brightness, 0.01);
+        assertEquals(0.2f, user2Brightness, 0.01);
+
+        // Validate saveIfNeeded writes user-specific brightnes.
+        final ByteArrayOutputStream baos = new ByteArrayOutputStream();
+        mInjector.setWriteStream(baos);
+        mDataStore.saveIfNeeded();
+        mTestLooper.dispatchAll();
+        assertTrue(mInjector.wasWriteSuccessful());
+        TestInjector newInjector = new TestInjector();
+        PersistentDataStore newDataStore = new PersistentDataStore(newInjector);
+        ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
+        newInjector.setReadStream(bais);
+        newDataStore.loadIfNeeded();
+
+        user1Brightness = newDataStore.getBrightness(testDisplayDevice, 1 /* userSerial */);
+        user2Brightness = newDataStore.getBrightness(testDisplayDevice, 2 /* userSerial */);
+        float unknownUserBrightness =
+                newDataStore.getBrightness(testDisplayDevice, 999 /* userSerial */);
+        assertEquals(0.1f, user1Brightness, 0.01);
+        assertEquals(0.2f, user2Brightness, 0.01);
+        assertTrue(Float.isNaN(unknownUserBrightness));
+    }
+
+    @Test
     public void testLoadingBrightnessConfigurations() {
         String contents = "<?xml version='1.0' encoding='utf-8' standalone='yes' ?>\n"
                 + "<display-manager-state>\n"
@@ -374,7 +482,7 @@
         ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
         newInjector.setReadStream(bais);
         newDataStore.loadIfNeeded();
-        assertTrue(Float.isNaN(mDataStore.getBrightness(testDisplayDevice)));
+        assertTrue(Float.isNaN(mDataStore.getBrightness(testDisplayDevice, 1 /* userSerial */)));
     }
 
     @Test
diff --git a/services/tests/servicestests/src/com/android/server/display/brightness/DisplayBrightnessControllerTest.java b/services/tests/servicestests/src/com/android/server/display/brightness/DisplayBrightnessControllerTest.java
index e6d3bbc..c4f4838 100644
--- a/services/tests/servicestests/src/com/android/server/display/brightness/DisplayBrightnessControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/brightness/DisplayBrightnessControllerTest.java
@@ -19,6 +19,7 @@
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.Mockito.clearInvocations;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
@@ -247,6 +248,7 @@
                 0.0f);
         verify(mBrightnessChangeExecutor).execute(mOnBrightnessChangeRunnable);
         verify(mBrightnessSetting).setBrightness(brightnessValue);
+        verify(mBrightnessSetting).setUserSerial(anyInt());
 
         // Does nothing if the value is invalid
         mDisplayBrightnessController.updateScreenBrightnessSetting(Float.NaN);
@@ -358,4 +360,28 @@
         verify(mBrightnessSetting, never()).getBrightnessNitsForDefaultDisplay();
         verify(mBrightnessSetting, never()).setBrightness(brightness);
     }
+
+    @Test
+    public void testChangeBrightnessNitsWhenUserChanges() {
+        float brightnessValue1 = 0.3f;
+        float nits1 = 200f;
+        float brightnessValue2 = 0.5f;
+        float nits2 = 300f;
+        AutomaticBrightnessController automaticBrightnessController =
+                mock(AutomaticBrightnessController.class);
+        when(automaticBrightnessController.convertToNits(brightnessValue1)).thenReturn(nits1);
+        when(automaticBrightnessController.convertToNits(brightnessValue2)).thenReturn(nits2);
+        mDisplayBrightnessController.setAutomaticBrightnessController(
+                automaticBrightnessController);
+
+        mDisplayBrightnessController.setBrightness(brightnessValue1, 1 /* user-serial */);
+        verify(mBrightnessSetting).setUserSerial(1);
+        verify(mBrightnessSetting).setBrightness(brightnessValue1);
+        verify(mBrightnessSetting).setBrightnessNitsForDefaultDisplay(nits1);
+
+        mDisplayBrightnessController.setBrightness(brightnessValue2, 2 /* user-serial */);
+        verify(mBrightnessSetting).setUserSerial(2);
+        verify(mBrightnessSetting).setBrightness(brightnessValue2);
+        verify(mBrightnessSetting).setBrightnessNitsForDefaultDisplay(nits2);
+    }
 }
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 4c10ddc..6668f85 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationVisitUrisTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationVisitUrisTest.java
@@ -87,7 +87,6 @@
     // This list should be emptied! Items can be removed as bugs are fixed.
     private static final Multimap<Class<?>, String> KNOWN_BAD =
             ImmutableMultimap.<Class<?>, String>builder()
-                    .put(RemoteViews.class, "setIcon") // b/281018094
                     .put(Notification.WearableExtender.class, "addAction") // TODO: b/281044385
                     .put(Person.Builder.class, "setUri") // TODO: b/281044385
                     .put(RemoteViews.class, "setRemoteAdapter") // TODO: b/281044385
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 a422c68..dd90e04 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTests.java
@@ -51,14 +51,18 @@
 import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.when;
 
+import android.graphics.Insets;
 import android.graphics.PixelFormat;
 import android.graphics.Rect;
 import android.platform.test.annotations.Presubmit;
 import android.view.DisplayInfo;
 import android.view.DisplayShape;
+import android.view.InsetsFrameProvider;
 import android.view.InsetsSource;
 import android.view.InsetsState;
 import android.view.PrivacyIndicatorBounds;
+import android.view.Surface;
+import android.view.WindowInsets;
 import android.view.WindowInsets.Side;
 import android.view.WindowManager;
 
@@ -388,6 +392,36 @@
                 && displayPolicy.updateDecorInsetsInfo());
         assertEquals(STATUS_BAR_HEIGHT, displayPolicy.getDecorInsetsInfo(di.rotation,
                 di.logicalWidth, di.logicalHeight).mConfigInsets.top);
+
+        // Add a window that provides the same insets in current rotation. But it specifies
+        // different insets in other rotations.
+        final WindowState bar2 = createWindow(null, statusBar.mAttrs.type, "bar2");
+        bar2.mAttrs.providedInsets = new InsetsFrameProvider[] {
+                new InsetsFrameProvider(bar2, 0, WindowInsets.Type.statusBars())
+                        .setInsetsSize(Insets.of(0, STATUS_BAR_HEIGHT, 0, 0))
+        };
+        bar2.mAttrs.setFitInsetsTypes(0);
+        bar2.mAttrs.paramsForRotation = new WindowManager.LayoutParams[4];
+        final int doubleHeightFor90 = STATUS_BAR_HEIGHT * 2;
+        for (int i = ROTATION_0; i <= Surface.ROTATION_270; i++) {
+            final WindowManager.LayoutParams params = new WindowManager.LayoutParams();
+            params.setFitInsetsTypes(0);
+            if (i == Surface.ROTATION_90) {
+                params.providedInsets = new InsetsFrameProvider[] {
+                        new InsetsFrameProvider(bar2, 0, WindowInsets.Type.statusBars())
+                                .setInsetsSize(Insets.of(0, doubleHeightFor90, 0, 0))
+                };
+            } else {
+                params.providedInsets = bar2.mAttrs.providedInsets;
+            }
+            bar2.mAttrs.paramsForRotation[i] = params;
+        }
+        displayPolicy.addWindowLw(bar2, bar2.mAttrs);
+        // Current rotation is 0 and the top insets is still STATUS_BAR_HEIGHT, so no change.
+        assertFalse(displayPolicy.updateDecorInsetsInfo());
+        // The insets in other rotations should be still updated.
+        assertEquals(doubleHeightFor90, displayPolicy.getDecorInsetsInfo(Surface.ROTATION_90,
+                di.logicalHeight, di.logicalWidth).mConfigInsets.top);
     }
 
     @SetupWindows(addWindows = { W_NAVIGATION_BAR, W_INPUT_METHOD })
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
index 863523f..ddc729f 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
@@ -485,6 +485,7 @@
                 new InsetsFrameProvider(owner, 0, WindowInsets.Type.statusBars())
                         .setInsetsSize(Insets.of(0, STATUS_BAR_HEIGHT, 0, 0))
         };
+        statusBar.mAttrs.setFitInsetsTypes(0);
         dc.getDisplayPolicy().addWindowLw(statusBar, statusBar.mAttrs);
         return statusBar;
     }
diff --git a/tests/FlickerTests/Android.bp b/tests/FlickerTests/Android.bp
index 2f96eec..4c55f7f 100644
--- a/tests/FlickerTests/Android.bp
+++ b/tests/FlickerTests/Android.bp
@@ -83,6 +83,7 @@
     ],
     data: [
         ":FlickerTestApp",
+        "trace_config/*",
     ],
 }
 
diff --git a/tests/FlickerTests/AndroidTestTemplate.xml b/tests/FlickerTests/AndroidTestTemplate.xml
index 1176828..ec01317 100644
--- a/tests/FlickerTests/AndroidTestTemplate.xml
+++ b/tests/FlickerTests/AndroidTestTemplate.xml
@@ -4,6 +4,9 @@
  -->
 <configuration description="Runs WindowManager {MODULE}">
     <option name="test-tag" value="FlickerTests" />
+    <!-- Needed for storing the perfetto trace files in the sdcard/test_results-->
+    <option name="isolated-storage" value="false" />
+
     <target_preparer class="com.android.tradefed.targetprep.DeviceSetup">
         <!-- keeps the screen on during tests -->
         <option name="screen-always-on" value="on" />
@@ -29,25 +32,51 @@
         <option name="run-command" value="settings put secure show_ime_with_hard_keyboard 1" />
         <option name="run-command" value="settings put system show_touches 1" />
         <option name="run-command" value="settings put system pointer_location 1" />
-        <option name="teardown-command" value="settings delete secure show_ime_with_hard_keyboard" />
+        <option name="teardown-command"
+                value="settings delete secure show_ime_with_hard_keyboard" />
         <option name="teardown-command" value="settings delete system show_touches" />
         <option name="teardown-command" value="settings delete system pointer_location" />
-        <option name="teardown-command" value="cmd overlay enable com.android.internal.systemui.navbar.gestural" />
+        <option name="teardown-command"
+                value="cmd overlay enable com.android.internal.systemui.navbar.gestural" />
     </target_preparer>
     <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
         <option name="cleanup-apks" value="true"/>
         <option name="test-file-name" value="{MODULE}.apk"/>
         <option name="test-file-name" value="FlickerTestApp.apk" />
     </target_preparer>
+    <!-- Needed for pushing the trace config file -->
+    <target_preparer class="com.android.tradefed.targetprep.RootTargetPreparer"/>
+    <target_preparer class="com.android.tradefed.targetprep.PushFilePreparer">
+        <option name="push-file"
+                key="trace_config.textproto"
+                value="/data/misc/perfetto-traces/trace_config.textproto"
+        />
+        <!--Install the content provider automatically when we push some file in sdcard folder.-->
+        <!--Needed to avoid the installation during the test suite.-->
+        <option name="push-file" key="trace_config.textproto" value="/sdcard/sample.textproto" />
+    </target_preparer>
     <test class="com.android.tradefed.testtype.AndroidJUnitTest">
         <option name="package" value="{PACKAGE}"/>
         <option name="shell-timeout" value="6600s" />
         <option name="test-timeout" value="6600s" />
         <option name="hidden-api-checks" value="false" />
+        <option name="device-listeners" value="android.device.collectors.PerfettoListener" />
+        <!-- PerfettoListener related arguments -->
+        <option name="instrumentation-arg" key="perfetto_config_text_proto" value="true" />
+        <option name="instrumentation-arg"
+                key="perfetto_config_file"
+                value="trace_config.textproto"
+        />
+        <option name="instrumentation-arg" key="per_run" value="true" />
     </test>
+    <!-- Needed for pulling the collected trace config on to the host -->
     <metrics_collector class="com.android.tradefed.device.metric.FilePullerLogCollector">
-        <option name="directory-keys" value="/sdcard/flicker" />
-        <option name="collect-on-run-ended-only" value="true" />
+        <option name="pull-pattern-keys" value="perfetto_file_path" />
+    </metrics_collector>
+    <metrics_collector class="com.android.tradefed.device.metric.FilePullerLogCollector">
+        <option name="pull-pattern-keys" value="(\w)+\.winscope" />
+        <option name="pull-pattern-keys" value="(\w)+\.mp4" />
+        <option name="collect-on-run-ended-only" value="false" />
         <option name="clean-up" value="true" />
     </metrics_collector>
 </configuration>
diff --git a/tests/FlickerTests/manifests/AndroidManifest.xml b/tests/FlickerTests/manifests/AndroidManifest.xml
index 5b00310..de8a3c6 100644
--- a/tests/FlickerTests/manifests/AndroidManifest.xml
+++ b/tests/FlickerTests/manifests/AndroidManifest.xml
@@ -48,9 +48,4 @@
         <uses-library android:name="android.test.runner"/>
         <uses-library android:name="androidx.window.extensions" android:required="false"/>
     </application>
-
-    <!--<instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
-                     android:targetPackage="{{package}}"
-                     android:label="WindowManager Flicker Tests {MODULE}">
-    </instrumentation>-->
 </manifest>
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/CommonAssertions.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/CommonAssertions.kt
index 1f120d4..ed9e14f 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/CommonAssertions.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/CommonAssertions.kt
@@ -97,19 +97,19 @@
     if (allStates) {
         assertLayers {
             this.invoke("entireScreenCovered") { entry ->
-                entry.entry.displays.forEach { display ->
+                entry.entry.displays.filter { it.isOn }.forEach { display ->
                     entry.visibleRegion().coversAtLeast(display.layerStackSpace)
                 }
             }
         }
     } else {
         assertLayersStart {
-            this.entry.displays.forEach { display ->
+            this.entry.displays.filter { it.isOn }.forEach { display ->
                 this.visibleRegion().coversAtLeast(display.layerStackSpace)
             }
         }
         assertLayersEnd {
-            this.entry.displays.forEach { display ->
+            this.entry.displays.filter { it.isOn }.forEach { display ->
                 this.visibleRegion().coversAtLeast(display.layerStackSpace)
             }
         }
diff --git a/tests/FlickerTests/trace_config/trace_config.textproto b/tests/FlickerTests/trace_config/trace_config.textproto
new file mode 100644
index 0000000..c9a35ac
--- /dev/null
+++ b/tests/FlickerTests/trace_config/trace_config.textproto
@@ -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.
+
+# proto-message: TraceConfig
+
+# Enable periodic flushing of the trace buffer into the output file.
+write_into_file: true
+
+# Writes the userspace buffer into the file every 1s.
+file_write_period_ms: 2500
+
+# See b/126487238 - we need to guarantee ordering of events.
+flush_period_ms: 30000
+
+# The trace buffers needs to be big enough to hold |file_write_period_ms| of
+# trace data. The trace buffer sizing depends on the number of trace categories
+# enabled and the device activity.
+
+# RSS events
+buffers: {
+  size_kb: 63488
+  fill_policy: RING_BUFFER
+}
+
+data_sources {
+  config {
+    name: "linux.process_stats"
+    target_buffer: 0
+    # polled per-process memory counters and process/thread names.
+    # If you don't want the polled counters, remove the "process_stats_config"
+    # section, but keep the data source itself as it still provides on-demand
+    # thread/process naming for ftrace data below.
+    process_stats_config {
+      scan_all_processes_on_start: true
+    }
+  }
+}
+
+data_sources: {
+  config {
+    name: "linux.ftrace"
+    ftrace_config {
+      ftrace_events: "ftrace/print"
+      ftrace_events: "task/task_newtask"
+      ftrace_events: "task/task_rename"
+      atrace_categories: "ss"
+      atrace_categories: "wm"
+      atrace_categories: "am"
+      atrace_categories: "aidl"
+      atrace_categories: "input"
+      atrace_categories: "binder_driver"
+      atrace_categories: "sched_process_exit"
+      atrace_apps: "com.android.server.wm.flicker"
+      atrace_apps: "com.android.server.wm.flicker.other"
+      atrace_apps: "com.android.server.wm.flicker.close"
+      atrace_apps: "com.android.server.wm.flicker.ime"
+      atrace_apps: "com.android.server.wm.flicker.launch"
+      atrace_apps: "com.android.server.wm.flicker.quickswitch"
+      atrace_apps: "com.android.server.wm.flicker.rotation"
+      atrace_apps: "com.android.server.wm.flicker.testapp"
+      atrace_apps: "com.android.systemui"
+      atrace_apps: "com.google.android.apps.nexuslauncher"
+    }
+  }
+}
+