Merge "[People Service] Fix `hasActiveNotifications` to track when notifications are modified" into tm-qpr-dev
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index 8b41aa4..46d8481 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -7944,8 +7944,6 @@
          * @hide
          */
         public MessagingStyle setShortcutIcon(@Nullable Icon conversationIcon) {
-            // TODO(b/228941516): This icon should be downscaled to avoid using too much memory,
-            // see reduceImageSizes.
             mShortcutIcon = conversationIcon;
             return this;
         }
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index a1a1c45..d8d0bb2 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -9838,9 +9838,11 @@
 
         /**
          * Whether or not a SFPS device is enabling the performant auth setting.
+         * The "_V2" suffix was added to re-introduce the default behavior for
+         * users. See b/265264294 fore more details.
          * @hide
          */
-        public static final String SFPS_PERFORMANT_AUTH_ENABLED = "sfps_performant_auth_enabled";
+        public static final String SFPS_PERFORMANT_AUTH_ENABLED = "sfps_performant_auth_enabled_v2";
 
         /**
          * Whether or not debugging is enabled.
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 1834d19..43bbcfb 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -73,6 +73,7 @@
 import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_FORCE_DECOR_VIEW_VISIBILITY;
 import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_INSET_PARENT_FRAME_BY_IME;
 import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_LAYOUT_SIZE_EXTENDED_BY_CUTOUT;
+import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_OPTIMIZE_MEASURE;
 import static android.view.WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE;
 import static android.view.WindowManager.LayoutParams.SOFT_INPUT_MASK_ADJUST;
 import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_STARTING;
@@ -2766,7 +2767,7 @@
      * TODO(b/260382739): Apply this to all windows.
      */
     private static boolean shouldOptimizeMeasure(final WindowManager.LayoutParams lp) {
-        return lp.type == TYPE_NOTIFICATION_SHADE;
+        return (lp.privateFlags & PRIVATE_FLAG_OPTIMIZE_MEASURE) != 0;
     }
 
     private Rect getWindowBoundsInsetSystemBars() {
diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java
index abc4926..a37c244 100644
--- a/core/java/android/view/WindowManager.java
+++ b/core/java/android/view/WindowManager.java
@@ -2580,6 +2580,15 @@
         public static final int PRIVATE_FLAG_SYSTEM_ERROR = 0x00000100;
 
         /**
+         * Flag to indicate that the view hierarchy of the window can only be measured when
+         * necessary. If a window size can be known by the LayoutParams, we can use the size to
+         * relayout window, and we don't have to measure the view hierarchy before laying out the
+         * views. This reduces the chances to perform measure.
+         * {@hide}
+         */
+        public static final int PRIVATE_FLAG_OPTIMIZE_MEASURE = 0x00000200;
+
+        /**
          * Flag that prevents the wallpaper behind the current window from receiving touch events.
          *
          * {@hide}
@@ -2781,6 +2790,7 @@
                 PRIVATE_FLAG_NO_MOVE_ANIMATION,
                 PRIVATE_FLAG_COMPATIBLE_WINDOW,
                 PRIVATE_FLAG_SYSTEM_ERROR,
+                PRIVATE_FLAG_OPTIMIZE_MEASURE,
                 PRIVATE_FLAG_DISABLE_WALLPAPER_TOUCH_EVENTS,
                 PRIVATE_FLAG_FORCE_SHOW_STATUS_BAR,
                 PRIVATE_FLAG_LAYOUT_SIZE_EXTENDED_BY_CUTOUT,
@@ -2841,6 +2851,10 @@
                         equals = PRIVATE_FLAG_SYSTEM_ERROR,
                         name = "SYSTEM_ERROR"),
                 @ViewDebug.FlagToString(
+                        mask = PRIVATE_FLAG_OPTIMIZE_MEASURE,
+                        equals = PRIVATE_FLAG_OPTIMIZE_MEASURE,
+                        name = "OPTIMIZE_MEASURE"),
+                @ViewDebug.FlagToString(
                         mask = PRIVATE_FLAG_DISABLE_WALLPAPER_TOUCH_EVENTS,
                         equals = PRIVATE_FLAG_DISABLE_WALLPAPER_TOUCH_EVENTS,
                         name = "DISABLE_WALLPAPER_TOUCH_EVENTS"),
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index c759239..5f0753bb 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -4976,9 +4976,8 @@
     <!-- If face auth sends the user directly to home/last open app, or stays on keyguard -->
     <bool name="config_faceAuthDismissesKeyguard">true</bool>
 
-    <!-- Default value for whether a SFPS device is required to be interactive for fingerprint auth
-    to unlock the device.  -->
-    <bool name="config_requireScreenOnToAuthEnabled">false</bool>
+    <!-- Default value for performant auth feature. -->
+    <bool name="config_performantAuthDefault">false</bool>
 
     <!-- The component name for the default profile supervisor, which can be set as a profile owner
     even after user setup is complete. The defined component should be used for supervision purposes
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 7bf0dfc..4d4be22 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -2725,7 +2725,7 @@
   <java-symbol type="array" name="config_face_acquire_vendor_biometricprompt_ignorelist" />
   <java-symbol type="bool" name="config_faceAuthSupportsSelfIllumination" />
   <java-symbol type="bool" name="config_faceAuthDismissesKeyguard" />
-  <java-symbol type="bool" name="config_requireScreenOnToAuthEnabled" />
+  <java-symbol type="bool" name="config_performantAuthDefault" />
 
   <!-- Face config -->
   <java-symbol type="integer" name="config_faceMaxTemplatesPerUser" />
diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml
index 75c92e0..c685d62 100644
--- a/packages/SystemUI/AndroidManifest.xml
+++ b/packages/SystemUI/AndroidManifest.xml
@@ -663,6 +663,20 @@
             android:excludeFromRecents="true"
             android:exported="true" />
 
+        <!-- started from Telecomm(CallsManager) -->
+        <activity
+            android:name=".telephony.ui.activity.SwitchToManagedProfileForCallActivity"
+            android:excludeFromRecents="true"
+            android:exported="true"
+            android:finishOnCloseSystemDialogs="true"
+            android:permission="android.permission.MODIFY_PHONE_STATE"
+            android:theme="@style/Theme.SystemUI.Dialog.Alert">
+            <intent-filter>
+                <action android:name="android.telecom.action.SHOW_SWITCH_TO_WORK_PROFILE_FOR_CALL_DIALOG" />
+                <category android:name="android.intent.category.DEFAULT" />
+            </intent-filter>
+        </activity>
+
         <!-- platform logo easter egg activity -->
         <activity
             android:name=".DessertCase"
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/DialogLaunchAnimator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/DialogLaunchAnimator.kt
index 9a9236b..a3ed085 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/DialogLaunchAnimator.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/DialogLaunchAnimator.kt
@@ -237,13 +237,17 @@
             openedDialogs.firstOrNull {
                 it.dialog.window.decorView.viewRootImpl == controller.viewRoot
             }
-        val animateFrom =
+        val controller =
             animatedParent?.dialogContentWithBackground?.let {
                 Controller.fromView(it, controller.cuj)
             }
                 ?: controller
 
-        if (animatedParent == null && animateFrom !is LaunchableView) {
+        if (
+            animatedParent == null &&
+                controller is ViewDialogLaunchAnimatorController &&
+                controller.source !is LaunchableView
+        ) {
             // Make sure the View we launch from implements LaunchableView to avoid visibility
             // issues. Given that we don't own dialog decorViews so we can't enforce it for launches
             // from a dialog.
@@ -272,7 +276,7 @@
                 launchAnimator,
                 callback,
                 interactionJankMonitor,
-                animateFrom,
+                controller,
                 onDialogDismissed = { openedDialogs.remove(it) },
                 dialog = dialog,
                 animateBackgroundBoundsChange,
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/ViewDialogLaunchAnimatorController.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/ViewDialogLaunchAnimatorController.kt
index 46d5a5c..9257f99 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/ViewDialogLaunchAnimatorController.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/ViewDialogLaunchAnimatorController.kt
@@ -25,7 +25,7 @@
 /** A [DialogLaunchAnimator.Controller] that can animate a [View] from/to a dialog. */
 class ViewDialogLaunchAnimatorController
 internal constructor(
-    private val source: View,
+    internal val source: View,
     override val cuj: DialogCuj?,
 ) : DialogLaunchAnimator.Controller {
     override val viewRoot: ViewRootImpl?
diff --git a/packages/SystemUI/res/layout/ongoing_privacy_chip.xml b/packages/SystemUI/res/layout/ongoing_privacy_chip.xml
index 5aa6080..d1a2cf4 100644
--- a/packages/SystemUI/res/layout/ongoing_privacy_chip.xml
+++ b/packages/SystemUI/res/layout/ongoing_privacy_chip.xml
@@ -25,6 +25,7 @@
     android:focusable="true"
     android:clipChildren="false"
     android:clipToPadding="false"
+    android:paddingStart="8dp"
     >
 
         <LinearLayout
diff --git a/packages/SystemUI/res/values-sw600dp-land/dimens.xml b/packages/SystemUI/res/values-sw600dp-land/dimens.xml
index 6c7cab5..5d78e4e 100644
--- a/packages/SystemUI/res/values-sw600dp-land/dimens.xml
+++ b/packages/SystemUI/res/values-sw600dp-land/dimens.xml
@@ -28,6 +28,7 @@
 
     <!-- QS-->
     <dimen name="qs_panel_padding_top">16dp</dimen>
+    <dimen name="qs_panel_padding">24dp</dimen>
     <dimen name="qs_content_horizontal_padding">24dp</dimen>
     <dimen name="qs_horizontal_margin">24dp</dimen>
     <!-- in split shade qs_tiles_page_horizontal_margin should be equal of qs_horizontal_margin/2,
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 2de16a4..43dea0a 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -1455,10 +1455,10 @@
     <string name="notification_conversation_summary_low">No sound or vibration and appears lower in conversation section</string>
 
     <!-- [CHAR LIMIT=150] Notification Importance title: normal importance level summary -->
-    <string name="notification_channel_summary_default">May ring or vibrate based on phone settings</string>
+    <string name="notification_channel_summary_default">May ring or vibrate based on device settings</string>
 
     <!-- [CHAR LIMIT=150] Conversation Notification Importance title: normal conversation level, with bubbling summary -->
-    <string name="notification_channel_summary_default_with_bubbles">May ring or vibrate based on phone settings. Conversations from <xliff:g id="app_name" example="YouTube">%1$s</xliff:g> bubble by default.</string>
+    <string name="notification_channel_summary_default_with_bubbles">May ring or vibrate based on device settings. Conversations from <xliff:g id="app_name" example="YouTube">%1$s</xliff:g> bubble by default.</string>
 
     <!-- [CHAR LIMIT=150] Notification Importance title: automatic importance level summary -->
     <string name="notification_channel_summary_automatic">Have the system determine if this notification should make sound or vibration</string>
@@ -2777,4 +2777,19 @@
     <string name="stylus_battery_low_percentage"><xliff:g id="percentage" example="16%">%s</xliff:g> battery remaining</string>
     <!-- Subtitle for the notification sent when a stylus battery is low. [CHAR LIMIT=none]-->
     <string name="stylus_battery_low_subtitle">Connect your stylus to a charger</string>
+
+    <!-- Switch to work profile dialer app for placing a call dialog. -->
+    <!-- Text for Switch to work profile dialog's Title. Switch to work profile dialog guide users to make call from work
+    profile dialer app as it's not possible to make call from current profile due to an admin policy. [CHAR LIMIT=60] -->
+    <string name="call_from_work_profile_title">Can\'t call from this profile</string>
+    <!-- Text for switch to work profile for call dialog to guide users to make call from work
+    profile dialer app as it's not possible to make call from current profile due to an admin policy. [CHAR LIMIT=NONE]
+    -->
+    <string name="call_from_work_profile_text">Your work policy allows you to make phone calls only from the work profile</string>
+    <!-- Label for the button to switch to work profile for placing call. Switch to work profile dialog guide users to make call from work
+    profile dialer app as it's not possible to make call from current profile due to an admin policy.[CHAR LIMIT=60] -->
+    <string name="call_from_work_profile_action">Switch to work profile</string>
+    <!-- Label for the close button on switch to work profile dialog. Switch to work profile dialog guide users to make call from work
+    profile dialer app as it's not possible to make call from current profile due to an admin policy.[CHAR LIMIT=60] -->
+    <string name="call_from_work_profile_close">Close</string>
 </resources>
diff --git a/packages/SystemUI/res/xml/large_screen_shade_header.xml b/packages/SystemUI/res/xml/large_screen_shade_header.xml
index 06d425c..bf576dc 100644
--- a/packages/SystemUI/res/xml/large_screen_shade_header.xml
+++ b/packages/SystemUI/res/xml/large_screen_shade_header.xml
@@ -1,5 +1,4 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
+<?xml version="1.0" encoding="utf-8"?><!--
   ~ Copyright (C) 2021 The Android Open Source Project
   ~
   ~ Licensed under the Apache License, Version 2.0 (the "License");
@@ -15,105 +14,73 @@
   ~ limitations under the License.
   -->
 
-<ConstraintSet
-    xmlns:android="http://schemas.android.com/apk/res/android"
+<ConstraintSet xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:app="http://schemas.android.com/apk/res-auto"
     android:id="@+id/large_screen_header_constraint">
 
-    <Constraint
-        android:id="@+id/clock">
+    <Constraint android:id="@+id/clock">
         <Layout
             android:layout_width="wrap_content"
             android:layout_height="0dp"
-            app:layout_constraintStart_toStartOf="parent"
-            app:layout_constraintTop_toTopOf="parent"
             app:layout_constraintBottom_toBottomOf="parent"
-            app:layout_constraintEnd_toStartOf="@id/date"
-            app:layout_constraintHorizontal_bias="0"
-        />
-        <Transform
-            android:scaleX="1"
-            android:scaleY="1"
-            />
+            app:layout_constraintStart_toEndOf="@id/begin_guide"
+            app:layout_constraintTop_toTopOf="parent" />
+        <PropertySet android:alpha="1" />
     </Constraint>
 
-    <Constraint
-        android:id="@+id/date">
+    <Constraint android:id="@+id/date">
         <Layout
             android:layout_width="wrap_content"
             android:layout_height="0dp"
+            android:layout_marginStart="8dp"
+            app:layout_constraintBottom_toBottomOf="parent"
             app:layout_constraintStart_toEndOf="@id/clock"
-            app:layout_constraintEnd_toStartOf="@id/carrier_group"
-            app:layout_constraintTop_toTopOf="parent"
-            app:layout_constraintBottom_toBottomOf="parent"
-            app:layout_constraintHorizontal_bias="0"
-        />
+            app:layout_constraintTop_toTopOf="parent" />
+        <PropertySet android:alpha="1" />
     </Constraint>
 
-    <Constraint
-        android:id="@+id/carrier_group">
+    <Constraint android:id="@+id/carrier_group">
         <Layout
-            app:layout_constraintWidth_min="48dp"
             android:layout_width="0dp"
             android:layout_height="0dp"
-            app:layout_constrainedWidth="true"
             android:layout_gravity="end|center_vertical"
-            android:layout_marginStart="8dp"
-            app:layout_constraintStart_toEndOf="@id/date"
-            app:layout_constraintEnd_toStartOf="@id/statusIcons"
-            app:layout_constraintTop_toTopOf="@id/clock"
             app:layout_constraintBottom_toBottomOf="parent"
-            app:layout_constraintHorizontal_bias="1"
-            />
-        <PropertySet
-            android:alpha="1"
-        />
+            app:layout_constraintEnd_toStartOf="@id/statusIcons"
+            app:layout_constraintStart_toEndOf="@id/date"
+            app:layout_constraintTop_toTopOf="parent"
+            app:layout_constraintWidth_default="wrap"
+            app:layout_constraintWidth_min="48dp" />
+        <PropertySet android:alpha="1" />
     </Constraint>
 
-    <Constraint
-        android:id="@+id/statusIcons">
+    <Constraint android:id="@+id/statusIcons">
         <Layout
-            app:layout_constraintHeight_min="@dimen/large_screen_shade_header_min_height"
             android:layout_width="wrap_content"
             android:layout_height="@dimen/large_screen_shade_header_min_height"
-            app:layout_constraintStart_toEndOf="@id/carrier_group"
-            app:layout_constraintEnd_toStartOf="@id/batteryRemainingIcon"
-            app:layout_constraintTop_toTopOf="@id/clock"
             app:layout_constraintBottom_toBottomOf="parent"
-            app:layout_constraintHorizontal_bias="1"
-            />
-        <PropertySet
-            android:alpha="1"
-        />
+            app:layout_constraintEnd_toStartOf="@id/batteryRemainingIcon"
+            app:layout_constraintTop_toTopOf="parent"
+            app:layout_constraintEnd_toEndOf="@id/carrier_group"/>
+        <PropertySet android:alpha="1" />
     </Constraint>
 
-    <Constraint
-        android:id="@+id/batteryRemainingIcon">
+    <Constraint android:id="@+id/batteryRemainingIcon">
         <Layout
             android:layout_width="wrap_content"
             android:layout_height="0dp"
             app:layout_constraintHeight_min="@dimen/large_screen_shade_header_min_height"
-            app:layout_constraintStart_toEndOf="@id/statusIcons"
-            app:layout_constraintEnd_toStartOf="@id/privacy_container"
-            app:layout_constraintTop_toTopOf="@id/clock"
             app:layout_constraintBottom_toBottomOf="parent"
-        />
-        <PropertySet
-            android:alpha="1"
-        />
+            app:layout_constraintEnd_toStartOf="@id/privacy_container"
+            app:layout_constraintTop_toTopOf="parent" />
+        <PropertySet android:alpha="1" />
     </Constraint>
 
-    <Constraint
-        android:id="@+id/privacy_container">
+    <Constraint android:id="@+id/privacy_container">
         <Layout
             android:layout_width="wrap_content"
             android:layout_height="@dimen/large_screen_shade_header_min_height"
-            app:layout_constraintEnd_toEndOf="parent"
-            app:layout_constraintTop_toTopOf="@id/date"
-            app:layout_constraintBottom_toBottomOf="@id/date"
-            app:layout_constraintStart_toEndOf="@id/batteryRemainingIcon"
-            app:layout_constraintHorizontal_bias="1"
-        />
+            app:layout_constraintBottom_toBottomOf="parent"
+            app:layout_constraintEnd_toStartOf="@id/end_guide"
+            app:layout_constraintTop_toTopOf="parent" />
     </Constraint>
-
 </ConstraintSet>
\ No newline at end of file
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/hardware/InputDevice.kt b/packages/SystemUI/shared/src/com/android/systemui/shared/hardware/InputDevice.kt
new file mode 100644
index 0000000..a9a5cf9
--- /dev/null
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/hardware/InputDevice.kt
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.shared.hardware
+
+import android.view.InputDevice
+
+/**
+ * Returns true if [InputDevice] is electronic components to allow a user to use an active stylus in
+ * the host device or a passive stylus is detected by the host device.
+ */
+val InputDevice.isInternalStylusSource: Boolean
+    get() = isAnyStylusSource && !isExternal
+
+/** Returns true if [InputDevice] is an active stylus. */
+val InputDevice.isExternalStylusSource: Boolean
+    get() = isAnyStylusSource && isExternal
+
+/**
+ * Returns true if [InputDevice] supports any stylus source.
+ *
+ * @see InputDevice.isInternalStylusSource
+ * @see InputDevice.isExternalStylusSource
+ */
+val InputDevice.isAnyStylusSource: Boolean
+    get() = supportsSource(InputDevice.SOURCE_STYLUS)
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/hardware/InputManager.kt b/packages/SystemUI/shared/src/com/android/systemui/shared/hardware/InputManager.kt
new file mode 100644
index 0000000..f020b4e
--- /dev/null
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/hardware/InputManager.kt
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.shared.hardware
+
+import android.hardware.input.InputManager
+import android.view.InputDevice
+
+/**
+ * Gets information about all input devices in the system and returns as a lazy [Sequence].
+ *
+ * For performance reasons, it is preferred to operate atop the returned [Sequence] to ensure each
+ * operation is executed on an element-per-element basis yet customizable.
+ *
+ * For example:
+ * ```kotlin
+ * val stylusDevices = inputManager.getInputDeviceSequence().filter {
+ *   it.supportsSource(InputDevice.SOURCE_STYLUS)
+ * }
+ *
+ * val hasInternalStylus = stylusDevices.any { it.isInternal }
+ * val hasExternalStylus = stylusDevices.any { !it.isInternal }
+ * ```
+ *
+ * @return a [Sequence] of [InputDevice].
+ */
+fun InputManager.getInputDeviceSequence(): Sequence<InputDevice> =
+    inputDeviceIds.asSequence().mapNotNull { getInputDevice(it) }
+
+/**
+ * Returns the first [InputDevice] matching the given predicate, or null if no such [InputDevice]
+ * was found.
+ */
+fun InputManager.findInputDevice(predicate: (InputDevice) -> Boolean): InputDevice? =
+    getInputDeviceSequence().find { predicate(it) }
+
+/**
+ * Returns true if [any] [InputDevice] matches with [predicate].
+ *
+ * For example:
+ * ```kotlin
+ * val hasStylusSupport = inputManager.hasInputDevice { it.isStylusSupport() }
+ * val hasStylusPen = inputManager.hasInputDevice { it.isStylusPen() }
+ * ```
+ */
+fun InputManager.hasInputDevice(predicate: (InputDevice) -> Boolean): Boolean =
+    getInputDeviceSequence().any { predicate(it) }
+
+/** Returns true if host device has any [InputDevice] where [InputDevice.isInternalStylusSource]. */
+fun InputManager.hasInternalStylusSource(): Boolean = hasInputDevice { it.isInternalStylusSource }
+
+/** Returns true if host device has any [InputDevice] where [InputDevice.isExternalStylusSource]. */
+fun InputManager.hasExternalStylusSource(): Boolean = hasInputDevice { it.isExternalStylusSource }
+
+/** Returns true if host device has any [InputDevice] where [InputDevice.isAnyStylusSource]. */
+fun InputManager.hasAnyStylusSource(): Boolean = hasInputDevice { it.isAnyStylusSource }
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
index 204f09e..a9695dd 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
@@ -86,7 +86,6 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
-import android.content.pm.IPackageManager;
 import android.content.pm.PackageManager;
 import android.content.pm.ResolveInfo;
 import android.content.pm.UserInfo;
@@ -110,7 +109,6 @@
 import android.os.Message;
 import android.os.PowerManager;
 import android.os.RemoteException;
-import android.os.ServiceManager;
 import android.os.SystemClock;
 import android.os.Trace;
 import android.os.UserHandle;
@@ -274,21 +272,6 @@
     private static final ComponentName FALLBACK_HOME_COMPONENT = new ComponentName(
             "com.android.settings", "com.android.settings.FallbackHome");
 
-    /**
-     * If true, the system is in the half-boot-to-decryption-screen state.
-     * Prudently disable lockscreen.
-     */
-    public static final boolean CORE_APPS_ONLY;
-
-    static {
-        try {
-            CORE_APPS_ONLY = IPackageManager.Stub.asInterface(
-                    ServiceManager.getService("package")).isOnlyCoreApps();
-        } catch (RemoteException e) {
-            throw e.rethrowFromSystemServer();
-        }
-    }
-
     private final Context mContext;
     private final UserTracker mUserTracker;
     private final KeyguardUpdateMonitorLogger mLogger;
diff --git a/packages/SystemUI/src/com/android/keyguard/logging/TrustRepositoryLogger.kt b/packages/SystemUI/src/com/android/keyguard/logging/TrustRepositoryLogger.kt
new file mode 100644
index 0000000..249b3fe
--- /dev/null
+++ b/packages/SystemUI/src/com/android/keyguard/logging/TrustRepositoryLogger.kt
@@ -0,0 +1,89 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.keyguard.logging
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.keyguard.shared.model.TrustModel
+import com.android.systemui.log.dagger.KeyguardUpdateMonitorLog
+import com.android.systemui.plugins.log.LogBuffer
+import com.android.systemui.plugins.log.LogLevel
+import javax.inject.Inject
+
+/** Logging helper for trust repository. */
+@SysUISingleton
+class TrustRepositoryLogger
+@Inject
+constructor(
+    @KeyguardUpdateMonitorLog private val logBuffer: LogBuffer,
+) {
+    fun onTrustChanged(
+        enabled: Boolean,
+        newlyUnlocked: Boolean,
+        userId: Int,
+        flags: Int,
+        trustGrantedMessages: List<String>?
+    ) {
+        logBuffer.log(
+            TAG,
+            LogLevel.DEBUG,
+            {
+                bool1 = enabled
+                bool2 = newlyUnlocked
+                int1 = userId
+                int2 = flags
+                str1 = trustGrantedMessages?.joinToString()
+            },
+            {
+                "onTrustChanged enabled: $bool1, newlyUnlocked: $bool2, " +
+                    "userId: $int1, flags: $int2, grantMessages: $str1"
+            }
+        )
+    }
+
+    fun trustListenerRegistered() {
+        logBuffer.log(TAG, LogLevel.VERBOSE, "TrustRepository#registerTrustListener")
+    }
+
+    fun trustListenerUnregistered() {
+        logBuffer.log(TAG, LogLevel.VERBOSE, "TrustRepository#unregisterTrustListener")
+    }
+
+    fun trustModelEmitted(value: TrustModel) {
+        logBuffer.log(
+            TAG,
+            LogLevel.DEBUG,
+            {
+                int1 = value.userId
+                bool1 = value.isTrusted
+            },
+            { "trustModel emitted: userId: $int1 isTrusted: $bool1" }
+        )
+    }
+
+    fun isCurrentUserTrusted(isCurrentUserTrusted: Boolean) {
+        logBuffer.log(
+            TAG,
+            LogLevel.DEBUG,
+            { bool1 = isCurrentUserTrusted },
+            { "isCurrentUserTrusted emitted: $bool1" }
+        )
+    }
+
+    companion object {
+        const val TAG = "TrustRepositoryLog"
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/SideFpsController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/SideFpsController.kt
index 1afa9b2..6f594d5 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/SideFpsController.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/SideFpsController.kt
@@ -111,7 +111,7 @@
         context.resources.getInteger(android.R.integer.config_mediumAnimTime).toLong()
 
     private val isReverseDefaultRotation =
-        context.getResources().getBoolean(com.android.internal.R.bool.config_reverseDefaultRotation)
+        context.resources.getBoolean(com.android.internal.R.bool.config_reverseDefaultRotation)
 
     private var overlayHideAnimator: ViewPropertyAnimator? = null
 
@@ -268,10 +268,12 @@
         val isDefaultOrientation =
             if (isReverseDefaultRotation) !isNaturalOrientation else isNaturalOrientation
         val size = windowManager.maximumWindowMetrics.bounds
+
         val displayWidth = if (isDefaultOrientation) size.width() else size.height()
         val displayHeight = if (isDefaultOrientation) size.height() else size.width()
         val boundsWidth = if (isDefaultOrientation) bounds.width() else bounds.height()
         val boundsHeight = if (isDefaultOrientation) bounds.height() else bounds.width()
+
         val sensorBounds =
             if (overlayOffsets.isYAligned()) {
                 Rect(
@@ -297,6 +299,7 @@
 
         overlayViewParams.x = sensorBounds.left
         overlayViewParams.y = sensorBounds.top
+
         windowManager.updateViewLayout(overlayView, overlayViewParams)
     }
 
@@ -306,7 +309,12 @@
         }
         // hide after a few seconds if the sensor is oriented down and there are
         // large overlapping system bars
-        val rotation = context.display?.rotation
+        var rotation = context.display?.rotation
+
+        if (rotation != null) {
+            rotation = getRotationFromDefault(rotation)
+        }
+
         if (
             windowManager.currentWindowMetrics.windowInsets.hasBigNavigationBar() &&
                 ((rotation == Surface.ROTATION_270 && overlayOffsets.isYAligned()) ||
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
index 432fae1..f0416e5 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
@@ -102,6 +102,10 @@
     val FILTER_UNSEEN_NOTIFS_ON_KEYGUARD =
         unreleasedFlag(254647461, "filter_unseen_notifs_on_keyguard", teamfood = true)
 
+    // TODO(b/263414400): Tracking Bug
+    @JvmField
+    val NOTIFICATION_ANIMATE_BIG_PICTURE = unreleasedFlag(120, "notification_animate_big_picture")
+
     // 200 - keyguard/lockscreen
     // ** Flag retired **
     // public static final BooleanFlag KEYGUARD_LAYOUT =
@@ -530,6 +534,18 @@
     val OUTPUT_SWITCHER_SHOW_API_ENABLED =
         unreleasedFlag(2503, "output_switcher_show_api_enabled", teamfood = true)
 
+    // 2600 - unfold transitions
+    // TODO(b/265764985): Tracking Bug
+    @Keep
+    @JvmField
+    val ENABLE_DARK_VIGNETTE_WHEN_FOLDING =
+        unreleasedFlag(2600, "enable_dark_vignette_when_folding")
+
     // TODO(b259590361): Tracking bug
     val EXPERIMENTAL_FLAG = unreleasedFlag(2, "exp_flag_release")
+
+    // 2600 - keyboard shortcut
+    // TODO(b/259352579): Tracking Bug
+    @JvmField
+    val SHORTCUT_LIST_SEARCH_LAYOUT = unreleasedFlag(2600, "shortcut_list_search_layout")
 }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index fe84ac5..9756cad 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -1912,12 +1912,6 @@
      * Enable the keyguard if the settings are appropriate.
      */
     private void doKeyguardLocked(Bundle options) {
-        if (KeyguardUpdateMonitor.CORE_APPS_ONLY) {
-            // Don't show keyguard during half-booted cryptkeeper stage.
-            if (DEBUG) Log.d(TAG, "doKeyguard: not showing because booting to cryptkeeper");
-            return;
-        }
-
         // if another app is disabling us, don't show
         if (!mExternallyEnabled) {
             if (DEBUG) Log.d(TAG, "doKeyguard: not showing because externally disabled");
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/TrustRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/TrustRepository.kt
new file mode 100644
index 0000000..d90f328
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/TrustRepository.kt
@@ -0,0 +1,99 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.data.repository
+
+import android.app.trust.TrustManager
+import com.android.keyguard.logging.TrustRepositoryLogger
+import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
+import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.keyguard.shared.model.TrustModel
+import com.android.systemui.user.data.repository.UserRepository
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.flow.onStart
+import kotlinx.coroutines.flow.shareIn
+
+/** Encapsulates any state relevant to trust agents and trust grants. */
+interface TrustRepository {
+    /** Flow representing whether the current user is trusted. */
+    val isCurrentUserTrusted: Flow<Boolean>
+}
+
+@SysUISingleton
+class TrustRepositoryImpl
+@Inject
+constructor(
+    @Application private val applicationScope: CoroutineScope,
+    private val userRepository: UserRepository,
+    private val trustManager: TrustManager,
+    private val logger: TrustRepositoryLogger,
+) : TrustRepository {
+    private val latestTrustModelForUser = mutableMapOf<Int, TrustModel>()
+
+    private val trust =
+        conflatedCallbackFlow {
+                val callback =
+                    object : TrustManager.TrustListener {
+                        override fun onTrustChanged(
+                            enabled: Boolean,
+                            newlyUnlocked: Boolean,
+                            userId: Int,
+                            flags: Int,
+                            grantMsgs: List<String>?
+                        ) {
+                            logger.onTrustChanged(enabled, newlyUnlocked, userId, flags, grantMsgs)
+                            trySendWithFailureLogging(
+                                TrustModel(enabled, userId),
+                                TrustRepositoryLogger.TAG,
+                                "onTrustChanged"
+                            )
+                        }
+
+                        override fun onTrustError(message: CharSequence?) = Unit
+
+                        override fun onTrustManagedChanged(enabled: Boolean, userId: Int) = Unit
+                    }
+                trustManager.registerTrustListener(callback)
+                logger.trustListenerRegistered()
+                awaitClose {
+                    logger.trustListenerUnregistered()
+                    trustManager.unregisterTrustListener(callback)
+                }
+            }
+            .onEach {
+                latestTrustModelForUser[it.userId] = it
+                logger.trustModelEmitted(it)
+            }
+            .shareIn(applicationScope, started = SharingStarted.Eagerly, replay = 1)
+
+    override val isCurrentUserTrusted: Flow<Boolean>
+        get() =
+            combine(trust, userRepository.selectedUserInfo, ::Pair)
+                .map { latestTrustModelForUser[it.second.id]?.isTrusted ?: false }
+                .distinctUntilChanged()
+                .onEach { logger.isCurrentUserTrusted(it) }
+                .onStart { emit(false) }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/TrustModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/TrustModel.kt
new file mode 100644
index 0000000..4fd14b1
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/TrustModel.kt
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.shared.model
+
+/** Represents the trust state */
+data class TrustModel(
+    /** If true, the system believes the environment to be trusted. */
+    val isTrusted: Boolean,
+    /** The user, for which the trust changed. */
+    val userId: Int,
+)
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
index c7bad63..eea0e4c 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
@@ -501,6 +501,15 @@
     }
 
     private void updateIsEnabled() {
+        try {
+            Trace.beginSection("EdgeBackGestureHandler#updateIsEnabled");
+            updateIsEnabledTraced();
+        } finally {
+            Trace.endSection();
+        }
+    }
+
+    private void updateIsEnabledTraced() {
         boolean isEnabled = mIsAttached && mIsGesturalModeEnabled;
         if (isEnabled == mIsEnabled) {
             return;
@@ -586,13 +595,18 @@
     }
 
     private void setEdgeBackPlugin(NavigationEdgeBackPlugin edgeBackPlugin) {
-        if (mEdgeBackPlugin != null) {
-            mEdgeBackPlugin.onDestroy();
+        try {
+            Trace.beginSection("setEdgeBackPlugin");
+            if (mEdgeBackPlugin != null) {
+                mEdgeBackPlugin.onDestroy();
+            }
+            mEdgeBackPlugin = edgeBackPlugin;
+            mEdgeBackPlugin.setBackCallback(mBackCallback);
+            mEdgeBackPlugin.setLayoutParams(createLayoutParams());
+            updateDisplaySize();
+        } finally {
+            Trace.endSection();
         }
-        mEdgeBackPlugin = edgeBackPlugin;
-        mEdgeBackPlugin.setBackCallback(mBackCallback);
-        mEdgeBackPlugin.setLayoutParams(createLayoutParams());
-        updateDisplaySize();
     }
 
     public boolean isHandlingGestures() {
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
index b21a485..4f94ed1 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
@@ -553,6 +553,10 @@
             Log.d(TAG, "adding OnComputeInternalInsetsListener");
         }
         mScreenshotView.getViewTreeObserver().addOnComputeInternalInsetsListener(mScreenshotView);
+        if (DEBUG_WINDOW) {
+            Log.d(TAG, "setContentView: " + mScreenshotView);
+        }
+        setContentView(mScreenshotView);
     }
 
     /**
@@ -634,6 +638,7 @@
 
         // The window is focusable by default
         setWindowFocusable(true);
+        mScreenshotView.requestFocus();
 
         // Wait until this window is attached to request because it is
         // the reference used to locate the target window (below).
@@ -691,10 +696,7 @@
                     mContext.getDrawable(R.drawable.overlay_badge_background), owner));
         }
         mScreenshotView.setScreenshot(mScreenBitmap, screenInsets);
-        if (DEBUG_WINDOW) {
-            Log.d(TAG, "setContentView: " + mScreenshotView);
-        }
-        setContentView(mScreenshotView);
+
         // ignore system bar insets for the purpose of window layout
         mWindow.getDecorView().setOnApplyWindowInsetsListener(
                 (v, insets) -> WindowInsets.CONSUMED);
diff --git a/packages/SystemUI/src/com/android/systemui/shade/CombinedShadeHeadersConstraintManagerImpl.kt b/packages/SystemUI/src/com/android/systemui/shade/CombinedShadeHeadersConstraintManagerImpl.kt
index 5011227..b3d31f2 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/CombinedShadeHeadersConstraintManagerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/CombinedShadeHeadersConstraintManagerImpl.kt
@@ -69,7 +69,8 @@
         }
         return ConstraintsChanges(
             qqsConstraintsChanges = change,
-            qsConstraintsChanges = change
+            qsConstraintsChanges = change,
+            largeScreenConstraintsChanges = change,
         )
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/shade/LargeScreenShadeHeaderController.kt b/packages/SystemUI/src/com/android/systemui/shade/LargeScreenShadeHeaderController.kt
index e406be1..8867637 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/LargeScreenShadeHeaderController.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/LargeScreenShadeHeaderController.kt
@@ -113,7 +113,7 @@
             QQS_HEADER_CONSTRAINT -> "QQS Header"
             QS_HEADER_CONSTRAINT -> "QS Header"
             LARGE_SCREEN_HEADER_CONSTRAINT -> "Large Screen Header"
-            else -> "Unknown state"
+            else -> "Unknown state $this"
         }
     }
 
@@ -296,6 +296,9 @@
 
     override fun onViewAttached() {
         privacyIconsController.chipVisibilityListener = chipVisibilityListener
+        updateVisibility()
+        updateTransition()
+
         if (header is MotionLayout) {
             header.setOnApplyWindowInsetsListener(insetListener)
             clock.addOnLayoutChangeListener { v, _, _, _, _, _, _, _, _ ->
@@ -308,9 +311,6 @@
         dumpManager.registerDumpable(this)
         configurationController.addCallback(configurationControllerListener)
         demoModeController.addCallback(demoModeReceiver)
-
-        updateVisibility()
-        updateTransition()
     }
 
     override fun onViewDetached() {
@@ -436,15 +436,14 @@
         header as MotionLayout
         if (largeScreenActive) {
             logInstantEvent("Large screen constraints set")
-            header.setTransition(HEADER_TRANSITION_ID)
-            header.transitionToStart()
+            header.setTransition(LARGE_SCREEN_HEADER_TRANSITION_ID)
         } else {
             logInstantEvent("Small screen constraints set")
             header.setTransition(HEADER_TRANSITION_ID)
-            header.transitionToStart()
-            updatePosition()
-            updateScrollY()
         }
+        header.jumpToState(header.startState)
+        updatePosition()
+        updateScrollY()
     }
 
     private fun updatePosition() {
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
index c0017bee..ce6fb14 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
@@ -450,6 +450,7 @@
     private float mDownY;
     private int mDisplayTopInset = 0; // in pixels
     private int mDisplayRightInset = 0; // in pixels
+    private int mDisplayLeftInset = 0; // in pixels
     private int mLargeScreenShadeHeaderHeight;
     private int mSplitShadeNotificationsScrimMarginBottom;
 
@@ -3011,7 +3012,7 @@
             // left bounds can ignore insets, it should always reach the edge of the screen
             return 0;
         } else {
-            return mNotificationStackScrollLayoutController.getLeft();
+            return mNotificationStackScrollLayoutController.getLeft() + mDisplayLeftInset;
         }
     }
 
@@ -3019,7 +3020,7 @@
         if (mIsFullWidth) {
             return mView.getRight() + mDisplayRightInset;
         } else {
-            return mNotificationStackScrollLayoutController.getRight();
+            return mNotificationStackScrollLayoutController.getRight() + mDisplayLeftInset;
         }
     }
 
@@ -3143,8 +3144,8 @@
 
         // Convert global clipping coordinates to local ones,
         // relative to NotificationStackScrollLayout
-        int nsslLeft = left - mNotificationStackScrollLayoutController.getLeft();
-        int nsslRight = right - mNotificationStackScrollLayoutController.getLeft();
+        int nsslLeft = calculateNsslLeft(left);
+        int nsslRight = calculateNsslRight(right);
         int nsslTop = getNotificationsClippingTopBounds(top);
         int nsslBottom = bottom - mNotificationStackScrollLayoutController.getTop();
         int bottomRadius = mSplitShadeEnabled ? radius : 0;
@@ -3153,6 +3154,22 @@
                 nsslLeft, nsslTop, nsslRight, nsslBottom, topRadius, bottomRadius);
     }
 
+    private int calculateNsslLeft(int nsslLeftAbsolute) {
+        int left = nsslLeftAbsolute - mNotificationStackScrollLayoutController.getLeft();
+        if (mIsFullWidth) {
+            return left;
+        }
+        return left - mDisplayLeftInset;
+    }
+
+    private int calculateNsslRight(int nsslRightAbsolute) {
+        int right = nsslRightAbsolute - mNotificationStackScrollLayoutController.getLeft();
+        if (mIsFullWidth) {
+            return right;
+        }
+        return right - mDisplayLeftInset;
+    }
+
     private int getNotificationsClippingTopBounds(int qsTop) {
         if (mSplitShadeEnabled && mExpandingFromHeadsUp) {
             // in split shade nssl has extra top margin so clipping at top 0 is not enough, we need
@@ -4550,6 +4567,7 @@
         ipw.print("mDownY="); ipw.println(mDownY);
         ipw.print("mDisplayTopInset="); ipw.println(mDisplayTopInset);
         ipw.print("mDisplayRightInset="); ipw.println(mDisplayRightInset);
+        ipw.print("mDisplayLeftInset="); ipw.println(mDisplayLeftInset);
         ipw.print("mLargeScreenShadeHeaderHeight="); ipw.println(mLargeScreenShadeHeaderHeight);
         ipw.print("mSplitShadeNotificationsScrimMarginBottom=");
         ipw.println(mSplitShadeNotificationsScrimMarginBottom);
@@ -5927,6 +5945,7 @@
         Insets combinedInsets = insets.getInsetsIgnoringVisibility(insetTypes);
         mDisplayTopInset = combinedInsets.top;
         mDisplayRightInset = combinedInsets.right;
+        mDisplayLeftInset = combinedInsets.left;
 
         mNavigationBarBottomHeight = insets.getStableInsetBottom();
         updateMaxHeadsUpTranslation();
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java
index 26f8b62..ab2e692 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java
@@ -19,6 +19,7 @@
 import static android.view.WindowInsetsController.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE;
 import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
 import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_BEHAVIOR_CONTROLLED;
+import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_OPTIMIZE_MEASURE;
 
 import static com.android.systemui.DejankUtils.whitelistIpcs;
 import static com.android.systemui.statusbar.NotificationRemoteInputManager.ENABLE_REMOTE_INPUT;
@@ -52,13 +53,13 @@
 import com.android.systemui.colorextraction.SysuiColorExtractor;
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dump.DumpManager;
+import com.android.systemui.dump.DumpsysTableLogger;
 import com.android.systemui.keyguard.KeyguardViewMediator;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.plugins.statusbar.StatusBarStateController.StateListener;
 import com.android.systemui.statusbar.NotificationShadeWindowController;
 import com.android.systemui.statusbar.StatusBarState;
 import com.android.systemui.statusbar.SysuiStatusBarStateController;
-import com.android.systemui.statusbar.phone.CentralSurfaces;
 import com.android.systemui.statusbar.phone.DozeParameters;
 import com.android.systemui.statusbar.phone.KeyguardBypassController;
 import com.android.systemui.statusbar.phone.ScreenOffAnimationController;
@@ -72,10 +73,8 @@
 import java.lang.ref.Reference;
 import java.lang.ref.WeakReference;
 import java.util.ArrayList;
-import java.util.HashSet;
 import java.util.List;
 import java.util.Objects;
-import java.util.Set;
 import java.util.function.Consumer;
 import java.util.stream.Collectors;
 
@@ -89,6 +88,7 @@
         Dumpable, ConfigurationListener {
 
     private static final String TAG = "NotificationShadeWindowController";
+    private static final int MAX_STATE_CHANGES_BUFFER_SIZE = 100;
 
     private final Context mContext;
     private final WindowManager mWindowManager;
@@ -108,7 +108,7 @@
     private boolean mHasTopUi;
     private boolean mHasTopUiChanged;
     private float mScreenBrightnessDoze;
-    private final State mCurrentState = new State();
+    private final NotificationShadeWindowState mCurrentState = new NotificationShadeWindowState();
     private OtherwisedCollapsedListener mListener;
     private ForcePluginOpenListener mForcePluginOpenListener;
     private Consumer<Integer> mScrimsVisibilityListener;
@@ -125,6 +125,9 @@
     private int mDeferWindowLayoutParams;
     private boolean mLastKeyguardRotationAllowed;
 
+    private final NotificationShadeWindowState.Buffer mStateBuffer =
+            new NotificationShadeWindowState.Buffer(MAX_STATE_CHANGES_BUFFER_SIZE);
+
     @Inject
     public NotificationShadeWindowControllerImpl(Context context, WindowManager windowManager,
             IActivityManager activityManager, DozeParameters dozeParameters,
@@ -210,8 +213,8 @@
 
     @VisibleForTesting
     void onShadeExpansionFullyChanged(Boolean isExpanded) {
-        if (mCurrentState.mPanelExpanded != isExpanded) {
-            mCurrentState.mPanelExpanded = isExpanded;
+        if (mCurrentState.panelExpanded != isExpanded) {
+            mCurrentState.panelExpanded = isExpanded;
             apply(mCurrentState);
         }
     }
@@ -251,6 +254,7 @@
         mLp.setTitle("NotificationShade");
         mLp.packageName = mContext.getPackageName();
         mLp.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
+        mLp.privateFlags |= PRIVATE_FLAG_OPTIMIZE_MEASURE;
 
         // We use BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE here, however, there is special logic in
         // window manager which disables the transient show behavior.
@@ -296,10 +300,10 @@
         mNotificationShadeView.setSystemUiVisibility(vis);
     }
 
-    private void applyKeyguardFlags(State state) {
-        final boolean keyguardOrAod = state.mKeyguardShowing
-                || (state.mDozing && mDozeParameters.getAlwaysOn());
-        if ((keyguardOrAod && !state.mBackdropShowing && !state.mLightRevealScrimOpaque)
+    private void applyKeyguardFlags(NotificationShadeWindowState state) {
+        final boolean keyguardOrAod = state.keyguardShowing
+                || (state.dozing && mDozeParameters.getAlwaysOn());
+        if ((keyguardOrAod && !state.mediaBackdropShowing && !state.lightRevealScrimOpaque)
                 || mKeyguardViewMediator.isAnimatingBetweenKeyguardAndSurfaceBehind()) {
             // Show the wallpaper if we're on keyguard/AOD and the wallpaper is not occluded by a
             // solid backdrop. Also, show it if we are currently animating between the
@@ -310,15 +314,15 @@
             mLpChanged.flags &= ~LayoutParams.FLAG_SHOW_WALLPAPER;
         }
 
-        if (state.mDozing) {
+        if (state.dozing) {
             mLpChanged.privateFlags |= LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS;
         } else {
             mLpChanged.privateFlags &= ~LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS;
         }
 
         if (mKeyguardPreferredRefreshRate > 0) {
-            boolean onKeyguard = state.mStatusBarState == StatusBarState.KEYGUARD
-                    && !state.mKeyguardFadingAway && !state.mKeyguardGoingAway;
+            boolean onKeyguard = state.statusBarState == StatusBarState.KEYGUARD
+                    && !state.keyguardFadingAway && !state.keyguardGoingAway;
             if (onKeyguard
                     && mAuthController.isUdfpsEnrolled(KeyguardUpdateMonitor.getCurrentUser())) {
                 // both max and min display refresh rate must be set to take effect:
@@ -332,9 +336,9 @@
                     (long) mKeyguardPreferredRefreshRate);
         } else if (mKeyguardMaxRefreshRate > 0) {
             boolean bypassOnKeyguard = mKeyguardBypassController.getBypassEnabled()
-                    && state.mStatusBarState == StatusBarState.KEYGUARD
-                    && !state.mKeyguardFadingAway && !state.mKeyguardGoingAway;
-            if (state.mDozing || bypassOnKeyguard) {
+                    && state.statusBarState == StatusBarState.KEYGUARD
+                    && !state.keyguardFadingAway && !state.keyguardGoingAway;
+            if (state.dozing || bypassOnKeyguard) {
                 mLpChanged.preferredMaxDisplayRefreshRate = mKeyguardMaxRefreshRate;
             } else {
                 mLpChanged.preferredMaxDisplayRefreshRate = 0;
@@ -343,7 +347,7 @@
                     (long) mLpChanged.preferredMaxDisplayRefreshRate);
         }
 
-        if (state.mBouncerShowing && !isDebuggable()) {
+        if (state.bouncerShowing && !isDebuggable()) {
             mLpChanged.flags |= LayoutParams.FLAG_SECURE;
         } else {
             mLpChanged.flags &= ~LayoutParams.FLAG_SECURE;
@@ -354,8 +358,8 @@
         return Build.IS_DEBUGGABLE;
     }
 
-    private void adjustScreenOrientation(State state) {
-        if (state.mBouncerShowing || state.isKeyguardShowingAndNotOccluded() || state.mDozing) {
+    private void adjustScreenOrientation(NotificationShadeWindowState state) {
+        if (state.bouncerShowing || state.isKeyguardShowingAndNotOccluded() || state.dozing) {
             if (mKeyguardStateController.isKeyguardScreenRotationAllowed()) {
                 mLpChanged.screenOrientation = ActivityInfo.SCREEN_ORIENTATION_USER;
             } else {
@@ -366,10 +370,10 @@
         }
     }
 
-    private void applyFocusableFlag(State state) {
-        boolean panelFocusable = state.mNotificationShadeFocusable && state.mPanelExpanded;
-        if (state.mBouncerShowing && (state.mKeyguardOccluded || state.mKeyguardNeedsInput)
-                || ENABLE_REMOTE_INPUT && state.mRemoteInputActive
+    private void applyFocusableFlag(NotificationShadeWindowState state) {
+        boolean panelFocusable = state.notificationShadeFocusable && state.panelExpanded;
+        if (state.bouncerShowing && (state.keyguardOccluded || state.keyguardNeedsInput)
+                || ENABLE_REMOTE_INPUT && state.remoteInputActive
                 // Make the panel focusable if we're doing the screen off animation, since the light
                 // reveal scrim is drawing in the panel and should consume touch events so that they
                 // don't go to the app behind.
@@ -379,7 +383,7 @@
         } else if (state.isKeyguardShowingAndNotOccluded() || panelFocusable) {
             mLpChanged.flags &= ~LayoutParams.FLAG_NOT_FOCUSABLE;
             // Make sure to remove FLAG_ALT_FOCUSABLE_IM when keyguard needs input.
-            if (state.mKeyguardNeedsInput && state.isKeyguardShowingAndNotOccluded()) {
+            if (state.keyguardNeedsInput && state.isKeyguardShowingAndNotOccluded()) {
                 mLpChanged.flags &= ~LayoutParams.FLAG_ALT_FOCUSABLE_IM;
             } else {
                 mLpChanged.flags |= LayoutParams.FLAG_ALT_FOCUSABLE_IM;
@@ -390,19 +394,19 @@
         }
     }
 
-    private void applyForceShowNavigationFlag(State state) {
-        if (state.mPanelExpanded || state.mBouncerShowing
-                || ENABLE_REMOTE_INPUT && state.mRemoteInputActive) {
+    private void applyForceShowNavigationFlag(NotificationShadeWindowState state) {
+        if (state.panelExpanded || state.bouncerShowing
+                || ENABLE_REMOTE_INPUT && state.remoteInputActive) {
             mLpChanged.privateFlags |= LayoutParams.PRIVATE_FLAG_STATUS_FORCE_SHOW_NAVIGATION;
         } else {
             mLpChanged.privateFlags &= ~LayoutParams.PRIVATE_FLAG_STATUS_FORCE_SHOW_NAVIGATION;
         }
     }
 
-    private void applyVisibility(State state) {
+    private void applyVisibility(NotificationShadeWindowState state) {
         boolean visible = isExpanded(state);
         mLogger.logApplyVisibility(visible);
-        if (state.mForcePluginOpen) {
+        if (state.forcePluginOpen) {
             if (mListener != null) {
                 mListener.setWouldOtherwiseCollapse(visible);
             }
@@ -418,16 +422,16 @@
         }
     }
 
-    private boolean isExpanded(State state) {
-        return !state.mForceCollapsed && (state.isKeyguardShowingAndNotOccluded()
-                || state.mPanelVisible || state.mKeyguardFadingAway || state.mBouncerShowing
-                || state.mHeadsUpShowing
-                || state.mScrimsVisibility != ScrimController.TRANSPARENT)
-                || state.mBackgroundBlurRadius > 0
-                || state.mLaunchingActivity;
+    private boolean isExpanded(NotificationShadeWindowState state) {
+        return !state.forceWindowCollapsed && (state.isKeyguardShowingAndNotOccluded()
+                || state.panelVisible || state.keyguardFadingAway || state.bouncerShowing
+                || state.headsUpNotificationShowing
+                || state.scrimsVisibility != ScrimController.TRANSPARENT)
+                || state.backgroundBlurRadius > 0
+                || state.launchingActivityFromNotification;
     }
 
-    private void applyFitsSystemWindows(State state) {
+    private void applyFitsSystemWindows(NotificationShadeWindowState state) {
         boolean fitsSystemWindows = !state.isKeyguardShowingAndNotOccluded();
         if (mNotificationShadeView != null
                 && mNotificationShadeView.getFitsSystemWindows() != fitsSystemWindows) {
@@ -436,21 +440,21 @@
         }
     }
 
-    private void applyUserActivityTimeout(State state) {
+    private void applyUserActivityTimeout(NotificationShadeWindowState state) {
         if (state.isKeyguardShowingAndNotOccluded()
-                && state.mStatusBarState == StatusBarState.KEYGUARD
-                && !state.mQsExpanded) {
-            mLpChanged.userActivityTimeout = state.mBouncerShowing
+                && state.statusBarState == StatusBarState.KEYGUARD
+                && !state.qsExpanded) {
+            mLpChanged.userActivityTimeout = state.bouncerShowing
                     ? KeyguardViewMediator.AWAKE_INTERVAL_BOUNCER_MS : mLockScreenDisplayTimeout;
         } else {
             mLpChanged.userActivityTimeout = -1;
         }
     }
 
-    private void applyInputFeatures(State state) {
+    private void applyInputFeatures(NotificationShadeWindowState state) {
         if (state.isKeyguardShowingAndNotOccluded()
-                && state.mStatusBarState == StatusBarState.KEYGUARD
-                && !state.mQsExpanded && !state.mForceUserActivity) {
+                && state.statusBarState == StatusBarState.KEYGUARD
+                && !state.qsExpanded && !state.forceUserActivity) {
             mLpChanged.inputFeatures |=
                     LayoutParams.INPUT_FEATURE_DISABLE_USER_ACTIVITY;
         } else {
@@ -459,7 +463,7 @@
         }
     }
 
-    private void applyStatusBarColorSpaceAgnosticFlag(State state) {
+    private void applyStatusBarColorSpaceAgnosticFlag(NotificationShadeWindowState state) {
         if (!isExpanded(state)) {
             mLpChanged.privateFlags |= LayoutParams.PRIVATE_FLAG_COLOR_SPACE_AGNOSTIC;
         } else {
@@ -485,8 +489,8 @@
         applyWindowLayoutParams();
     }
 
-    private void apply(State state) {
-        mLogger.logNewState(state);
+    private void apply(NotificationShadeWindowState state) {
+        logState(state);
         applyKeyguardFlags(state);
         applyFocusableFlag(state);
         applyForceShowNavigationFlag(state);
@@ -515,6 +519,38 @@
         notifyStateChangedCallbacks();
     }
 
+    private void logState(NotificationShadeWindowState state) {
+        mStateBuffer.insert(
+                state.keyguardShowing,
+                state.keyguardOccluded,
+                state.keyguardNeedsInput,
+                state.panelVisible,
+                state.panelExpanded,
+                state.notificationShadeFocusable,
+                state.bouncerShowing,
+                state.keyguardFadingAway,
+                state.keyguardGoingAway,
+                state.qsExpanded,
+                state.headsUpNotificationShowing,
+                state.lightRevealScrimOpaque,
+                state.forceWindowCollapsed,
+                state.forceDozeBrightness,
+                state.forceUserActivity,
+                state.launchingActivityFromNotification,
+                state.mediaBackdropShowing,
+                state.wallpaperSupportsAmbientMode,
+                state.windowNotTouchable,
+                state.componentsForcingTopUi,
+                state.forceOpenTokens,
+                state.statusBarState,
+                state.remoteInputActive,
+                state.forcePluginOpen,
+                state.dozing,
+                state.scrimsVisibility,
+                state.backgroundBlurRadius
+        );
+    }
+
     @Override
     public void notifyStateChangedCallbacks() {
         // Copy callbacks to separate ArrayList to avoid concurrent modification
@@ -523,36 +559,36 @@
                 .filter(Objects::nonNull)
                 .collect(Collectors.toList());
         for (StatusBarWindowCallback cb : activeCallbacks) {
-            cb.onStateChanged(mCurrentState.mKeyguardShowing,
-                    mCurrentState.mKeyguardOccluded,
-                    mCurrentState.mBouncerShowing,
-                    mCurrentState.mDozing,
-                    mCurrentState.mPanelExpanded);
+            cb.onStateChanged(mCurrentState.keyguardShowing,
+                    mCurrentState.keyguardOccluded,
+                    mCurrentState.bouncerShowing,
+                    mCurrentState.dozing,
+                    mCurrentState.panelExpanded);
         }
     }
 
-    private void applyModalFlag(State state) {
-        if (state.mHeadsUpShowing) {
+    private void applyModalFlag(NotificationShadeWindowState state) {
+        if (state.headsUpNotificationShowing) {
             mLpChanged.flags |= LayoutParams.FLAG_NOT_TOUCH_MODAL;
         } else {
             mLpChanged.flags &= ~LayoutParams.FLAG_NOT_TOUCH_MODAL;
         }
     }
 
-    private void applyBrightness(State state) {
-        if (state.mForceDozeBrightness) {
+    private void applyBrightness(NotificationShadeWindowState state) {
+        if (state.forceDozeBrightness) {
             mLpChanged.screenBrightness = mScreenBrightnessDoze;
         } else {
             mLpChanged.screenBrightness = LayoutParams.BRIGHTNESS_OVERRIDE_NONE;
         }
     }
 
-    private void applyHasTopUi(State state) {
-        mHasTopUiChanged = !state.mComponentsForcingTopUi.isEmpty() || isExpanded(state);
+    private void applyHasTopUi(NotificationShadeWindowState state) {
+        mHasTopUiChanged = !state.componentsForcingTopUi.isEmpty() || isExpanded(state);
     }
 
-    private void applyNotTouchable(State state) {
-        if (state.mNotTouchable) {
+    private void applyNotTouchable(NotificationShadeWindowState state) {
+        if (state.windowNotTouchable) {
             mLpChanged.flags |= LayoutParams.FLAG_NOT_TOUCHABLE;
         } else {
             mLpChanged.flags &= ~LayoutParams.FLAG_NOT_TOUCHABLE;
@@ -574,88 +610,88 @@
 
     @Override
     public void setKeyguardShowing(boolean showing) {
-        mCurrentState.mKeyguardShowing = showing;
+        mCurrentState.keyguardShowing = showing;
         apply(mCurrentState);
     }
 
     @Override
     public void setKeyguardOccluded(boolean occluded) {
-        mCurrentState.mKeyguardOccluded = occluded;
+        mCurrentState.keyguardOccluded = occluded;
         apply(mCurrentState);
     }
 
     @Override
     public void setKeyguardNeedsInput(boolean needsInput) {
-        mCurrentState.mKeyguardNeedsInput = needsInput;
+        mCurrentState.keyguardNeedsInput = needsInput;
         apply(mCurrentState);
     }
 
     @Override
     public void setPanelVisible(boolean visible) {
-        if (mCurrentState.mPanelVisible == visible
-                && mCurrentState.mNotificationShadeFocusable == visible) {
+        if (mCurrentState.panelVisible == visible
+                && mCurrentState.notificationShadeFocusable == visible) {
             return;
         }
         mLogger.logShadeVisibleAndFocusable(visible);
-        mCurrentState.mPanelVisible = visible;
-        mCurrentState.mNotificationShadeFocusable = visible;
+        mCurrentState.panelVisible = visible;
+        mCurrentState.notificationShadeFocusable = visible;
         apply(mCurrentState);
     }
 
     @Override
     public void setNotificationShadeFocusable(boolean focusable) {
         mLogger.logShadeFocusable(focusable);
-        mCurrentState.mNotificationShadeFocusable = focusable;
+        mCurrentState.notificationShadeFocusable = focusable;
         apply(mCurrentState);
     }
 
     @Override
     public void setBouncerShowing(boolean showing) {
-        mCurrentState.mBouncerShowing = showing;
+        mCurrentState.bouncerShowing = showing;
         apply(mCurrentState);
     }
 
     @Override
     public void setBackdropShowing(boolean showing) {
-        mCurrentState.mBackdropShowing = showing;
+        mCurrentState.mediaBackdropShowing = showing;
         apply(mCurrentState);
     }
 
     @Override
     public void setKeyguardFadingAway(boolean keyguardFadingAway) {
-        mCurrentState.mKeyguardFadingAway = keyguardFadingAway;
+        mCurrentState.keyguardFadingAway = keyguardFadingAway;
         apply(mCurrentState);
     }
 
     private void onQsExpansionChanged(Boolean expanded) {
-        mCurrentState.mQsExpanded = expanded;
+        mCurrentState.qsExpanded = expanded;
         apply(mCurrentState);
     }
 
     @Override
     public void setForceUserActivity(boolean forceUserActivity) {
-        mCurrentState.mForceUserActivity = forceUserActivity;
+        mCurrentState.forceUserActivity = forceUserActivity;
         apply(mCurrentState);
     }
 
     @Override
     public void setLaunchingActivity(boolean launching) {
-        mCurrentState.mLaunchingActivity = launching;
+        mCurrentState.launchingActivityFromNotification = launching;
         apply(mCurrentState);
     }
 
     @Override
     public boolean isLaunchingActivity() {
-        return mCurrentState.mLaunchingActivity;
+        return mCurrentState.launchingActivityFromNotification;
     }
 
     @Override
     public void setScrimsVisibility(int scrimsVisibility) {
-        if (scrimsVisibility == mCurrentState.mScrimsVisibility) {
+        if (scrimsVisibility == mCurrentState.scrimsVisibility) {
             return;
         }
         boolean wasExpanded = isExpanded(mCurrentState);
-        mCurrentState.mScrimsVisibility = scrimsVisibility;
+        mCurrentState.scrimsVisibility = scrimsVisibility;
         if (wasExpanded != isExpanded(mCurrentState)) {
             apply(mCurrentState);
         }
@@ -669,31 +705,31 @@
      */
     @Override
     public void setBackgroundBlurRadius(int backgroundBlurRadius) {
-        if (mCurrentState.mBackgroundBlurRadius == backgroundBlurRadius) {
+        if (mCurrentState.backgroundBlurRadius == backgroundBlurRadius) {
             return;
         }
-        mCurrentState.mBackgroundBlurRadius = backgroundBlurRadius;
+        mCurrentState.backgroundBlurRadius = backgroundBlurRadius;
         apply(mCurrentState);
     }
 
     @Override
     public void setHeadsUpShowing(boolean showing) {
-        mCurrentState.mHeadsUpShowing = showing;
+        mCurrentState.headsUpNotificationShowing = showing;
         apply(mCurrentState);
     }
 
     @Override
     public void setLightRevealScrimOpaque(boolean opaque) {
-        if (mCurrentState.mLightRevealScrimOpaque == opaque) {
+        if (mCurrentState.lightRevealScrimOpaque == opaque) {
             return;
         }
-        mCurrentState.mLightRevealScrimOpaque = opaque;
+        mCurrentState.lightRevealScrimOpaque = opaque;
         apply(mCurrentState);
     }
 
     @Override
     public void setWallpaperSupportsAmbientMode(boolean supportsAmbientMode) {
-        mCurrentState.mWallpaperSupportsAmbientMode = supportsAmbientMode;
+        mCurrentState.wallpaperSupportsAmbientMode = supportsAmbientMode;
         apply(mCurrentState);
     }
 
@@ -701,7 +737,7 @@
      * @param state The {@link StatusBarStateController} of the status bar.
      */
     private void setStatusBarState(int state) {
-        mCurrentState.mStatusBarState = state;
+        mCurrentState.statusBarState = state;
         apply(mCurrentState);
     }
 
@@ -712,13 +748,13 @@
      */
     @Override
     public void setForceWindowCollapsed(boolean force) {
-        mCurrentState.mForceCollapsed = force;
+        mCurrentState.forceWindowCollapsed = force;
         apply(mCurrentState);
     }
 
     @Override
     public void onRemoteInputActive(boolean remoteInputActive) {
-        mCurrentState.mRemoteInputActive = remoteInputActive;
+        mCurrentState.remoteInputActive = remoteInputActive;
         apply(mCurrentState);
     }
 
@@ -728,32 +764,32 @@
      */
     @Override
     public void setForceDozeBrightness(boolean forceDozeBrightness) {
-        if (mCurrentState.mForceDozeBrightness == forceDozeBrightness) {
+        if (mCurrentState.forceDozeBrightness == forceDozeBrightness) {
             return;
         }
-        mCurrentState.mForceDozeBrightness = forceDozeBrightness;
+        mCurrentState.forceDozeBrightness = forceDozeBrightness;
         apply(mCurrentState);
     }
 
     @Override
     public void setDozing(boolean dozing) {
-        mCurrentState.mDozing = dozing;
+        mCurrentState.dozing = dozing;
         apply(mCurrentState);
     }
 
     @Override
     public void setForcePluginOpen(boolean forceOpen, Object token) {
         if (forceOpen) {
-            mCurrentState.mForceOpenTokens.add(token);
+            mCurrentState.forceOpenTokens.add(token);
         } else {
-            mCurrentState.mForceOpenTokens.remove(token);
+            mCurrentState.forceOpenTokens.remove(token);
         }
-        final boolean previousForceOpenState = mCurrentState.mForcePluginOpen;
-        mCurrentState.mForcePluginOpen = !mCurrentState.mForceOpenTokens.isEmpty();
-        if (previousForceOpenState != mCurrentState.mForcePluginOpen) {
+        final boolean previousForceOpenState = mCurrentState.forcePluginOpen;
+        mCurrentState.forcePluginOpen = !mCurrentState.forceOpenTokens.isEmpty();
+        if (previousForceOpenState != mCurrentState.forcePluginOpen) {
             apply(mCurrentState);
             if (mForcePluginOpenListener != null) {
-                mForcePluginOpenListener.onChange(mCurrentState.mForcePluginOpen);
+                mForcePluginOpenListener.onChange(mCurrentState.forcePluginOpen);
             }
         }
     }
@@ -763,12 +799,12 @@
      */
     @Override
     public boolean getForcePluginOpen() {
-        return mCurrentState.mForcePluginOpen;
+        return mCurrentState.forcePluginOpen;
     }
 
     @Override
     public void setNotTouchable(boolean notTouchable) {
-        mCurrentState.mNotTouchable = notTouchable;
+        mCurrentState.windowNotTouchable = notTouchable;
         apply(mCurrentState);
     }
 
@@ -777,7 +813,7 @@
      */
     @Override
     public boolean getPanelExpanded() {
-        return mCurrentState.mPanelExpanded;
+        return mCurrentState.panelExpanded;
     }
 
     @Override
@@ -800,11 +836,16 @@
         if (mNotificationShadeView != null && mNotificationShadeView.getViewRootImpl() != null) {
             mNotificationShadeView.getViewRootImpl().dump("  ", pw);
         }
+        new DumpsysTableLogger(
+                TAG,
+                NotificationShadeWindowState.TABLE_HEADERS,
+                mStateBuffer.toList()
+        ).printTableData(pw);
     }
 
     @Override
     public boolean isShowingWallpaper() {
-        return !mCurrentState.mBackdropShowing;
+        return !mCurrentState.mediaBackdropShowing;
     }
 
     @Override
@@ -834,7 +875,7 @@
      */
     @Override
     public void setKeyguardGoingAway(boolean goingAway) {
-        mCurrentState.mKeyguardGoingAway = goingAway;
+        mCurrentState.keyguardGoingAway = goingAway;
         apply(mCurrentState);
     }
 
@@ -846,87 +887,13 @@
     @Override
     public void setRequestTopUi(boolean requestTopUi, String componentTag) {
         if (requestTopUi) {
-            mCurrentState.mComponentsForcingTopUi.add(componentTag);
+            mCurrentState.componentsForcingTopUi.add(componentTag);
         } else {
-            mCurrentState.mComponentsForcingTopUi.remove(componentTag);
+            mCurrentState.componentsForcingTopUi.remove(componentTag);
         }
         apply(mCurrentState);
     }
 
-    private static class State {
-        boolean mKeyguardShowing;
-        boolean mKeyguardOccluded;
-        boolean mKeyguardNeedsInput;
-        boolean mPanelVisible;
-        boolean mPanelExpanded;
-        boolean mNotificationShadeFocusable;
-        boolean mBouncerShowing;
-        boolean mKeyguardFadingAway;
-        boolean mKeyguardGoingAway;
-        boolean mQsExpanded;
-        boolean mHeadsUpShowing;
-        boolean mLightRevealScrimOpaque;
-        boolean mForceCollapsed;
-        boolean mForceDozeBrightness;
-        boolean mForceUserActivity;
-        boolean mLaunchingActivity;
-        boolean mBackdropShowing;
-        boolean mWallpaperSupportsAmbientMode;
-        boolean mNotTouchable;
-        Set<String> mComponentsForcingTopUi = new HashSet<>();
-        Set<Object> mForceOpenTokens = new HashSet<>();
-
-        /**
-         * The status bar state from {@link CentralSurfaces}.
-         */
-        int mStatusBarState;
-
-        boolean mRemoteInputActive;
-        boolean mForcePluginOpen;
-        boolean mDozing;
-        int mScrimsVisibility;
-        int mBackgroundBlurRadius;
-
-        private boolean isKeyguardShowingAndNotOccluded() {
-            return mKeyguardShowing && !mKeyguardOccluded;
-        }
-
-        @Override
-        public String toString() {
-            return new StringBuilder()
-                    .append("State{")
-                    .append("  mKeyguardShowing=").append(mKeyguardShowing)
-                    .append(", mKeyguardOccluded=").append(mKeyguardOccluded)
-                    .append(", mKeyguardNeedsInput=").append(mKeyguardNeedsInput)
-                    .append(", mPanelVisible=").append(mPanelVisible)
-                    .append(", mPanelExpanded=").append(mPanelExpanded)
-                    .append(", mNotificationShadeFocusable=").append(mNotificationShadeFocusable)
-                    .append(", mBouncerShowing=").append(mBouncerShowing)
-                    .append(", mKeyguardFadingAway=").append(mKeyguardFadingAway)
-                    .append(", mKeyguardGoingAway=").append(mKeyguardGoingAway)
-                    .append(", mQsExpanded=").append(mQsExpanded)
-                    .append(", mHeadsUpShowing=").append(mHeadsUpShowing)
-                    .append(", mLightRevealScrimOpaque=").append(mLightRevealScrimOpaque)
-                    .append(", mForceCollapsed=").append(mForceCollapsed)
-                    .append(", mForceDozeBrightness=").append(mForceDozeBrightness)
-                    .append(", mForceUserActivity=").append(mForceUserActivity)
-                    .append(", mLaunchingActivity=").append(mLaunchingActivity)
-                    .append(", mBackdropShowing=").append(mBackdropShowing)
-                    .append(", mWallpaperSupportsAmbientMode=")
-                    .append(mWallpaperSupportsAmbientMode)
-                    .append(", mNotTouchable=").append(mNotTouchable)
-                    .append(", mComponentsForcingTopUi=").append(mComponentsForcingTopUi)
-                    .append(", mForceOpenTokens=").append(mForceOpenTokens)
-                    .append(", mStatusBarState=").append(mStatusBarState)
-                    .append(", mRemoteInputActive=").append(mRemoteInputActive)
-                    .append(", mForcePluginOpen=").append(mForcePluginOpen)
-                    .append(", mDozing=").append(mDozing)
-                    .append(", mScrimsVisibility=").append(mScrimsVisibility)
-                    .append(", mBackgroundBlurRadius=").append(mBackgroundBlurRadius)
-                    .append('}').toString();
-        }
-    }
-
     private final StateListener mStateListener = new StateListener() {
         @Override
         public void onStateChanged(int newState) {
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowState.kt b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowState.kt
new file mode 100644
index 0000000..736404aa
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowState.kt
@@ -0,0 +1,214 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.shade
+
+import com.android.systemui.dump.DumpsysTableLogger
+import com.android.systemui.dump.Row
+import com.android.systemui.plugins.util.RingBuffer
+import com.android.systemui.shade.NotificationShadeWindowState.Buffer
+import com.android.systemui.statusbar.StatusBarState
+
+/**
+ * Represents state of shade window, used by [NotificationShadeWindowControllerImpl].
+ * Contains nested class [Buffer] for pretty table logging in bug reports.
+ */
+class NotificationShadeWindowState(
+    @JvmField var keyguardShowing: Boolean = false,
+    @JvmField var keyguardOccluded: Boolean = false,
+    @JvmField var keyguardNeedsInput: Boolean = false,
+    @JvmField var panelVisible: Boolean = false,
+    /** shade panel is expanded (expansion fraction > 0) */
+    @JvmField var panelExpanded: Boolean = false,
+    @JvmField var notificationShadeFocusable: Boolean = false,
+    @JvmField var bouncerShowing: Boolean = false,
+    @JvmField var keyguardFadingAway: Boolean = false,
+    @JvmField var keyguardGoingAway: Boolean = false,
+    @JvmField var qsExpanded: Boolean = false,
+    @JvmField var headsUpNotificationShowing: Boolean = false,
+    @JvmField var lightRevealScrimOpaque: Boolean = false,
+    @JvmField var forceWindowCollapsed: Boolean = false,
+    @JvmField var forceDozeBrightness: Boolean = false,
+    // TODO: forceUserActivity seems to be unused, delete?
+    @JvmField var forceUserActivity: Boolean = false,
+    @JvmField var launchingActivityFromNotification: Boolean = false,
+    @JvmField var mediaBackdropShowing: Boolean = false,
+    @JvmField var wallpaperSupportsAmbientMode: Boolean = false,
+    @JvmField var windowNotTouchable: Boolean = false,
+    @JvmField var componentsForcingTopUi: MutableSet<String> = mutableSetOf(),
+    @JvmField var forceOpenTokens: MutableSet<Any> = mutableSetOf(),
+    /** one of [StatusBarState] */
+    @JvmField var statusBarState: Int = 0,
+    @JvmField var remoteInputActive: Boolean = false,
+    @JvmField var forcePluginOpen: Boolean = false,
+    @JvmField var dozing: Boolean = false,
+    @JvmField var scrimsVisibility: Int = 0,
+    @JvmField var backgroundBlurRadius: Int = 0,
+) {
+
+    fun isKeyguardShowingAndNotOccluded(): Boolean {
+        return keyguardShowing && !keyguardOccluded
+    }
+
+    /** List of [String] to be used as a [Row] with [DumpsysTableLogger]. */
+    val asStringList: List<String> by lazy {
+        listOf(
+            keyguardShowing.toString(),
+            keyguardOccluded.toString(),
+            keyguardNeedsInput.toString(),
+            panelVisible.toString(),
+            panelExpanded.toString(),
+            notificationShadeFocusable.toString(),
+            bouncerShowing.toString(),
+            keyguardFadingAway.toString(),
+            keyguardGoingAway.toString(),
+            qsExpanded.toString(),
+            headsUpNotificationShowing.toString(),
+            lightRevealScrimOpaque.toString(),
+            forceWindowCollapsed.toString(),
+            forceDozeBrightness.toString(),
+            forceUserActivity.toString(),
+            launchingActivityFromNotification.toString(),
+            mediaBackdropShowing.toString(),
+            wallpaperSupportsAmbientMode.toString(),
+            windowNotTouchable.toString(),
+            componentsForcingTopUi.toString(),
+            forceOpenTokens.toString(),
+            StatusBarState.toString(statusBarState),
+            remoteInputActive.toString(),
+            forcePluginOpen.toString(),
+            dozing.toString(),
+            scrimsVisibility.toString(),
+            backgroundBlurRadius.toString()
+        )
+    }
+
+    /**
+     * [RingBuffer] to store [NotificationShadeWindowState]. After the buffer is full, it will
+     * recycle old events.
+     */
+    class Buffer(capacity: Int) {
+
+        private val buffer = RingBuffer(capacity) { NotificationShadeWindowState() }
+
+        /** Insert a new element in the buffer. */
+        fun insert(
+            keyguardShowing: Boolean,
+            keyguardOccluded: Boolean,
+            keyguardNeedsInput: Boolean,
+            panelVisible: Boolean,
+            panelExpanded: Boolean,
+            notificationShadeFocusable: Boolean,
+            bouncerShowing: Boolean,
+            keyguardFadingAway: Boolean,
+            keyguardGoingAway: Boolean,
+            qsExpanded: Boolean,
+            headsUpShowing: Boolean,
+            lightRevealScrimOpaque: Boolean,
+            forceCollapsed: Boolean,
+            forceDozeBrightness: Boolean,
+            forceUserActivity: Boolean,
+            launchingActivity: Boolean,
+            backdropShowing: Boolean,
+            wallpaperSupportsAmbientMode: Boolean,
+            notTouchable: Boolean,
+            componentsForcingTopUi: MutableSet<String>,
+            forceOpenTokens: MutableSet<Any>,
+            statusBarState: Int,
+            remoteInputActive: Boolean,
+            forcePluginOpen: Boolean,
+            dozing: Boolean,
+            scrimsVisibility: Int,
+            backgroundBlurRadius: Int,
+        ) {
+            buffer.advance().apply {
+                this.keyguardShowing = keyguardShowing
+                this.keyguardOccluded = keyguardOccluded
+                this.keyguardNeedsInput = keyguardNeedsInput
+                this.panelVisible = panelVisible
+                this.panelExpanded = panelExpanded
+                this.notificationShadeFocusable = notificationShadeFocusable
+                this.bouncerShowing = bouncerShowing
+                this.keyguardFadingAway = keyguardFadingAway
+                this.keyguardGoingAway = keyguardGoingAway
+                this.qsExpanded = qsExpanded
+                this.headsUpNotificationShowing = headsUpShowing
+                this.lightRevealScrimOpaque = lightRevealScrimOpaque
+                this.forceWindowCollapsed = forceCollapsed
+                this.forceDozeBrightness = forceDozeBrightness
+                this.forceUserActivity = forceUserActivity
+                this.launchingActivityFromNotification = launchingActivity
+                this.mediaBackdropShowing = backdropShowing
+                this.wallpaperSupportsAmbientMode = wallpaperSupportsAmbientMode
+                this.windowNotTouchable = notTouchable
+                this.componentsForcingTopUi.clear()
+                this.componentsForcingTopUi.addAll(componentsForcingTopUi)
+                this.forceOpenTokens.clear()
+                this.forceOpenTokens.addAll(forceOpenTokens)
+                this.statusBarState = statusBarState
+                this.remoteInputActive = remoteInputActive
+                this.forcePluginOpen = forcePluginOpen
+                this.dozing = dozing
+                this.scrimsVisibility = scrimsVisibility
+                this.backgroundBlurRadius = backgroundBlurRadius
+            }
+        }
+
+        /**
+         * Returns the content of the buffer (sorted from latest to newest).
+         *
+         * @see [NotificationShadeWindowState.asStringList]
+         */
+        fun toList(): List<Row> {
+            return buffer.asSequence().map { it.asStringList }.toList()
+        }
+    }
+
+    companion object {
+        /** Headers for dumping a table using [DumpsysTableLogger]. */
+        @JvmField
+        val TABLE_HEADERS =
+            listOf(
+                "keyguardShowing",
+                "keyguardOccluded",
+                "keyguardNeedsInput",
+                "panelVisible",
+                "panelExpanded",
+                "notificationShadeFocusable",
+                "bouncerShowing",
+                "keyguardFadingAway",
+                "keyguardGoingAway",
+                "qsExpanded",
+                "headsUpShowing",
+                "lightRevealScrimOpaque",
+                "forceCollapsed",
+                "forceDozeBrightness",
+                "forceUserActivity",
+                "launchingActivity",
+                "backdropShowing",
+                "wallpaperSupportsAmbientMode",
+                "notTouchable",
+                "componentsForcingTopUi",
+                "forceOpenTokens",
+                "statusBarState",
+                "remoteInputActive",
+                "forcePluginOpen",
+                "dozing",
+                "scrimsVisibility",
+                "backgroundBlurRadius"
+            )
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
index cd423dc..4e9f69c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
@@ -23,8 +23,6 @@
 import static android.view.Display.DEFAULT_DISPLAY;
 import static android.view.Display.INVALID_DISPLAY;
 
-import static com.android.systemui.statusbar.phone.CentralSurfacesImpl.ONLY_CORE_APPS;
-
 import android.annotation.Nullable;
 import android.app.ITransientNotificationCallback;
 import android.app.StatusBarManager;
@@ -55,6 +53,7 @@
 import android.os.Process;
 import android.os.RemoteException;
 import android.util.Pair;
+import android.util.Slog;
 import android.util.SparseArray;
 import android.view.InsetsState.InternalInsetsType;
 import android.view.InsetsVisibilities;
@@ -543,8 +542,7 @@
         final int disabled1 = getDisabled1(DEFAULT_DISPLAY);
         final int disabled2 = getDisabled2(DEFAULT_DISPLAY);
         return (disabled1 & StatusBarManager.DISABLE_EXPAND) == 0
-                && (disabled2 & StatusBarManager.DISABLE2_NOTIFICATION_SHADE) == 0
-                && !ONLY_CORE_APPS;
+                && (disabled2 & StatusBarManager.DISABLE2_NOTIFICATION_SHADE) == 0;
     }
 
     @Override
@@ -1273,7 +1271,8 @@
     public void showMediaOutputSwitcher(String packageName) {
         int callingUid = Binder.getCallingUid();
         if (callingUid != 0 && callingUid != Process.SYSTEM_UID) {
-            throw new SecurityException("Call only allowed from system server.");
+            Slog.e(TAG, "Call only allowed from system server.");
+            return;
         }
         synchronized (mLock) {
             SomeArgs args = SomeArgs.obtain();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcutsModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcutsModule.java
new file mode 100644
index 0000000..a797d4a
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcutsModule.java
@@ -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.statusbar;
+
+import android.content.BroadcastReceiver;
+
+import dagger.Binds;
+import dagger.Module;
+import dagger.multibindings.ClassKey;
+import dagger.multibindings.IntoMap;
+
+/**
+ * Module for {@link com.android.systemui.KeyboardShortcutsReceiver}.
+ */
+@Module
+public abstract class KeyboardShortcutsModule {
+
+    /**
+     *
+     */
+    @Binds
+    @IntoMap
+    @ClassKey(KeyboardShortcutsReceiver.class)
+    public abstract BroadcastReceiver bindKeyboardShortcutsReceiver(
+            KeyboardShortcutsReceiver broadcastReceiver);
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
index 9f50aef..9275e2b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
@@ -79,6 +79,8 @@
 import com.android.systemui.R;
 import com.android.systemui.animation.Interpolators;
 import com.android.systemui.classifier.FalsingCollector;
+import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.flags.Flags;
 import com.android.systemui.plugins.FalsingManager;
 import com.android.systemui.plugins.PluginListener;
 import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin;
@@ -177,6 +179,7 @@
     private PeopleNotificationIdentifier mPeopleNotificationIdentifier;
     private Optional<BubblesManager> mBubblesManagerOptional;
     private MetricsLogger mMetricsLogger;
+    private FeatureFlags mFeatureFlags;
     private int mIconTransformContentShift;
     private int mMaxHeadsUpHeightBeforeN;
     private int mMaxHeadsUpHeightBeforeP;
@@ -277,7 +280,7 @@
     private boolean mChildIsExpanding;
 
     private boolean mJustClicked;
-    private boolean mIconAnimationRunning;
+    private boolean mAnimationRunning;
     private boolean mShowNoBackground;
     private ExpandableNotificationRow mNotificationParent;
     private OnExpandClickListener mOnExpandClickListener;
@@ -451,10 +454,26 @@
         return mPublicLayout;
     }
 
-    public void setIconAnimationRunning(boolean running) {
-        for (NotificationContentView l : mLayouts) {
-            setIconAnimationRunning(running, l);
+    /**
+     * Sets animations running in the layouts of this row, including public, private, and children.
+     * @param running whether the animations should be started running or stopped.
+     */
+    public void setAnimationRunning(boolean running) {
+        // Sets animations running in the private/public layouts.
+        if (mFeatureFlags.isEnabled(Flags.NOTIFICATION_ANIMATE_BIG_PICTURE)) {
+            for (NotificationContentView l : mLayouts) {
+                if (l != null) {
+                    l.setContentAnimationRunning(running);
+                    setIconAnimationRunning(running, l);
+                }
+            }
+        } else {
+            for (NotificationContentView l : mLayouts) {
+                setIconAnimationRunning(running, l);
+            }
         }
+        // For groups summaries with children, we want to set the children containers
+        // animating as well.
         if (mIsSummaryWithChildren) {
             NotificationViewWrapper viewWrapper = mChildrenContainer.getNotificationViewWrapper();
             if (viewWrapper != null) {
@@ -468,12 +487,18 @@
                     mChildrenContainer.getAttachedChildren();
             for (int i = 0; i < notificationChildren.size(); i++) {
                 ExpandableNotificationRow child = notificationChildren.get(i);
-                child.setIconAnimationRunning(running);
+                child.setAnimationRunning(running);
             }
         }
-        mIconAnimationRunning = running;
+        mAnimationRunning = running;
     }
 
+    /**
+     * Starts or stops animations of the icons in all potential content views (regardless of
+     * whether they're contracted, expanded, etc).
+     *
+     * @param running whether to start or stop the icon's animation.
+     */
     private void setIconAnimationRunning(boolean running, NotificationContentView layout) {
         if (layout != null) {
             View contractedChild = layout.getContractedChild();
@@ -485,16 +510,29 @@
         }
     }
 
+    /**
+     * Starts or stops animations of the icon in the provided view's icon and right icon.
+     *
+     * @param running whether to start or stop the icon's animation.
+     * @param child   the view with the icon to start or stop.
+     */
     private void setIconAnimationRunningForChild(boolean running, View child) {
         if (child != null) {
             ImageView icon = child.findViewById(com.android.internal.R.id.icon);
-            setIconRunning(icon, running);
+            setImageViewAnimationRunning(icon, running);
             ImageView rightIcon = child.findViewById(com.android.internal.R.id.right_icon);
-            setIconRunning(rightIcon, running);
+            setImageViewAnimationRunning(rightIcon, running);
         }
     }
 
-    private void setIconRunning(ImageView imageView, boolean running) {
+    /**
+     * Starts or stops the animation of a provided image view if it's an AnimationDrawable or an
+     * AnimatedVectorDrawable.
+     *
+     * @param imageView the image view on which to start/stop animation.
+     * @param running   whether to start or stop the view's animation.
+     */
+    private void setImageViewAnimationRunning(ImageView imageView, boolean running) {
         if (imageView != null) {
             Drawable drawable = imageView.getDrawable();
             if (drawable instanceof AnimationDrawable) {
@@ -561,8 +599,8 @@
             mChildrenContainer.recreateNotificationHeader(mExpandClickListener, isConversation());
             mChildrenContainer.onNotificationUpdated();
         }
-        if (mIconAnimationRunning) {
-            setIconAnimationRunning(true);
+        if (mAnimationRunning) {
+            setAnimationRunning(true);
         }
         if (mLastChronometerRunning) {
             setChronometerRunning(true);
@@ -1038,7 +1076,7 @@
             notifyHeightChanged(false /* needsAnimation */);
         }
         if (pinned) {
-            setIconAnimationRunning(true);
+            setAnimationRunning(true);
             mExpandedWhenPinned = false;
         } else if (mExpandedWhenPinned) {
             setUserExpanded(true);
@@ -1627,6 +1665,11 @@
         );
     }
 
+    /**
+     * Constructs an ExpandableNotificationRow.
+     * @param context context passed to image resolver
+     * @param attrs attributes used to initialize parent view
+     */
     public ExpandableNotificationRow(Context context, AttributeSet attrs) {
         super(context, attrs);
         mImageResolver = new NotificationInlineImageResolver(context,
@@ -1662,7 +1705,8 @@
             NotificationGutsManager gutsManager,
             MetricsLogger metricsLogger,
             SmartReplyConstants smartReplyConstants,
-            SmartReplyController smartReplyController) {
+            SmartReplyController smartReplyController,
+            FeatureFlags featureFlags) {
         mEntry = entry;
         mAppName = appName;
         if (mMenuRow == null) {
@@ -1697,6 +1741,7 @@
         mBubblesManagerOptional = bubblesManagerOptional;
         mNotificationGutsManager = gutsManager;
         mMetricsLogger = metricsLogger;
+        mFeatureFlags = featureFlags;
     }
 
     private void initDimens() {
@@ -3588,11 +3633,13 @@
     @VisibleForTesting
     protected void setPrivateLayout(NotificationContentView privateLayout) {
         mPrivateLayout = privateLayout;
+        mLayouts = new NotificationContentView[]{mPrivateLayout, mPublicLayout};
     }
 
     @VisibleForTesting
     protected void setPublicLayout(NotificationContentView publicLayout) {
         mPublicLayout = publicLayout;
+        mLayouts = new NotificationContentView[]{mPrivateLayout, mPublicLayout};
     }
 
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java
index d113860..bb92dfc 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java
@@ -219,7 +219,8 @@
                 mNotificationGutsManager,
                 mMetricsLogger,
                 mSmartReplyConstants,
-                mSmartReplyController
+                mSmartReplyController,
+                mFeatureFlags
         );
         mView.setDescendantFocusability(ViewGroup.FOCUS_BLOCK_DESCENDANTS);
         if (mAllowLongPress) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java
index e46bf52..4a023c4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java
@@ -184,6 +184,8 @@
     private boolean mRemoteInputVisible;
     private int mUnrestrictedContentHeight;
 
+    private boolean mContentAnimating;
+
     public NotificationContentView(Context context, AttributeSet attrs) {
         super(context, attrs);
         mHybridGroupManager = new HybridGroupManager(getContext());
@@ -2129,8 +2131,49 @@
         return false;
     }
 
+    /**
+     * Starts and stops animations in the underlying views.
+     * Avoids restarting the animations by checking whether they're already running first.
+     * Return value is used for testing.
+     *
+     * @param running whether to start animations running, or stop them.
+     * @return true if the state of animations changed.
+     */
+    public boolean setContentAnimationRunning(boolean running) {
+        boolean stateChangeRequired = (running != mContentAnimating);
+        if (stateChangeRequired) {
+            // Starts or stops the animations in the potential views.
+            if (mContractedWrapper != null) {
+                mContractedWrapper.setAnimationsRunning(running);
+            }
+            if (mExpandedWrapper != null) {
+                mExpandedWrapper.setAnimationsRunning(running);
+            }
+            if (mHeadsUpWrapper != null) {
+                mHeadsUpWrapper.setAnimationsRunning(running);
+            }
+            // Updates the state tracker.
+            mContentAnimating = running;
+            return true;
+        }
+        return false;
+    }
+
     private static class RemoteInputViewData {
         @Nullable RemoteInputView mView;
         @Nullable RemoteInputViewController mController;
     }
+
+    @VisibleForTesting
+    protected void setContractedWrapper(NotificationViewWrapper contractedWrapper) {
+        mContractedWrapper = contractedWrapper;
+    }
+    @VisibleForTesting
+    protected void setExpandedWrapper(NotificationViewWrapper expandedWrapper) {
+        mExpandedWrapper = expandedWrapper;
+    }
+    @VisibleForTesting
+    protected void setHeadsUpWrapper(NotificationViewWrapper headsUpWrapper) {
+        mHeadsUpWrapper = headsUpWrapper;
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationBigPictureTemplateViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationBigPictureTemplateViewWrapper.java
index 8732696..175ba15 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationBigPictureTemplateViewWrapper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationBigPictureTemplateViewWrapper.java
@@ -18,11 +18,15 @@
 
 import android.app.Notification;
 import android.content.Context;
+import android.graphics.drawable.AnimatedImageDrawable;
+import android.graphics.drawable.Drawable;
 import android.graphics.drawable.Icon;
 import android.os.Bundle;
 import android.service.notification.StatusBarNotification;
 import android.view.View;
 
+import com.android.internal.R;
+import com.android.internal.widget.BigPictureNotificationImageView;
 import com.android.systemui.statusbar.notification.ImageTransformState;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
 
@@ -31,6 +35,8 @@
  */
 public class NotificationBigPictureTemplateViewWrapper extends NotificationTemplateViewWrapper {
 
+    private BigPictureNotificationImageView mImageView;
+
     protected NotificationBigPictureTemplateViewWrapper(Context ctx, View view,
             ExpandableNotificationRow row) {
         super(ctx, view, row);
@@ -39,9 +45,14 @@
     @Override
     public void onContentUpdated(ExpandableNotificationRow row) {
         super.onContentUpdated(row);
+        resolveViews();
         updateImageTag(row.getEntry().getSbn());
     }
 
+    private void resolveViews() {
+        mImageView = mView.findViewById(R.id.big_picture);
+    }
+
     private void updateImageTag(StatusBarNotification sbn) {
         final Bundle extras = sbn.getNotification().extras;
         Icon bigLargeIcon = extras.getParcelable(Notification.EXTRA_LARGE_ICON_BIG, Icon.class);
@@ -54,4 +65,25 @@
             mRightIcon.setTag(ImageTransformState.ICON_TAG, getLargeIcon(sbn.getNotification()));
         }
     }
+
+    /**
+     * Starts or stops the animations in any drawables contained in this BigPicture Notification.
+     *
+     * @param running Whether the animations should be set to run.
+     */
+    @Override
+    public void setAnimationsRunning(boolean running) {
+        if (mImageView == null) {
+            return;
+        }
+        Drawable d = mImageView.getDrawable();
+        if (d instanceof AnimatedImageDrawable) {
+            AnimatedImageDrawable animatedImageDrawable = (AnimatedImageDrawable) d;
+            if (running) {
+                animatedImageDrawable.start();
+            } else {
+                animatedImageDrawable.stop();
+            }
+        }
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationConversationTemplateViewWrapper.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationConversationTemplateViewWrapper.kt
index e136055..10753f2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationConversationTemplateViewWrapper.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationConversationTemplateViewWrapper.kt
@@ -17,16 +17,20 @@
 package com.android.systemui.statusbar.notification.row.wrapper
 
 import android.content.Context
+import android.graphics.drawable.AnimatedImageDrawable
 import android.view.View
 import android.view.ViewGroup
 import com.android.internal.widget.CachingIconView
 import com.android.internal.widget.ConversationLayout
+import com.android.internal.widget.MessagingGroup
+import com.android.internal.widget.MessagingImageMessage
 import com.android.internal.widget.MessagingLinearLayout
 import com.android.systemui.R
 import com.android.systemui.statusbar.notification.NotificationFadeAware
 import com.android.systemui.statusbar.notification.NotificationUtils
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
 import com.android.systemui.statusbar.notification.row.wrapper.NotificationMessagingTemplateViewWrapper.setCustomImageMessageTransform
+import com.android.systemui.util.children
 
 /**
  * Wraps a notification containing a conversation template
@@ -49,6 +53,7 @@
     private lateinit var expandBtn: View
     private lateinit var expandBtnContainer: View
     private lateinit var imageMessageContainer: ViewGroup
+    private lateinit var messageContainers: ArrayList<MessagingGroup>
     private lateinit var messagingLinearLayout: MessagingLinearLayout
     private lateinit var conversationTitleView: View
     private lateinit var importanceRing: View
@@ -60,6 +65,7 @@
     private fun resolveViews() {
         messagingLinearLayout = conversationLayout.messagingLinearLayout
         imageMessageContainer = conversationLayout.imageMessageContainer
+        messageContainers = conversationLayout.messagingGroups
         with(conversationLayout) {
             conversationIconContainer =
                     requireViewById(com.android.internal.R.id.conversation_icon_container)
@@ -146,4 +152,26 @@
         NotificationFadeAware.setLayerTypeForFaded(expandBtn, faded)
         NotificationFadeAware.setLayerTypeForFaded(conversationIconContainer, faded)
     }
+
+    // Starts or stops the animations in any drawables contained in this Conversation Notification.
+    override fun setAnimationsRunning(running: Boolean) {
+        // We apply to both the child message containers in a conversation group,
+        // and the top level image message container.
+        val containers = messageContainers.asSequence().map { it.messageContainer } +
+                sequenceOf(imageMessageContainer)
+        val drawables =
+                containers
+                        .flatMap { it.children }
+                        .mapNotNull { child ->
+                            (child as? MessagingImageMessage)?.let { imageMessage ->
+                                imageMessage.drawable as? AnimatedImageDrawable
+                            }
+                        }
+        drawables.toSet().forEach {
+            when {
+                running -> it.start()
+                !running -> it.stop()
+            }
+        }
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationMessagingTemplateViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationMessagingTemplateViewWrapper.java
index c587ce0..4592fde 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationMessagingTemplateViewWrapper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationMessagingTemplateViewWrapper.java
@@ -17,9 +17,13 @@
 package com.android.systemui.statusbar.notification.row.wrapper;
 
 import android.content.Context;
+import android.graphics.drawable.AnimatedImageDrawable;
+import android.graphics.drawable.Drawable;
 import android.view.View;
 import android.view.ViewGroup;
 
+import com.android.internal.widget.MessagingGroup;
+import com.android.internal.widget.MessagingImageMessage;
 import com.android.internal.widget.MessagingLayout;
 import com.android.internal.widget.MessagingLinearLayout;
 import com.android.systemui.R;
@@ -127,4 +131,40 @@
         }
         return super.getMinLayoutHeight();
     }
+
+    /**
+     * Starts or stops the animations in any drawables contained in this Messaging Notification.
+     *
+     * @param running Whether the animations should be set to run.
+     */
+    @Override
+    public void setAnimationsRunning(boolean running) {
+        if (mMessagingLayout == null) {
+            return;
+        }
+
+        for (MessagingGroup group : mMessagingLayout.getMessagingGroups()) {
+            for (int i = 0; i < group.getMessageContainer().getChildCount(); i++) {
+                View view = group.getMessageContainer().getChildAt(i);
+                // We only need to set animations in MessagingImageMessages.
+                if (!(view instanceof MessagingImageMessage)) {
+                    continue;
+                }
+                MessagingImageMessage imageMessage =
+                        (com.android.internal.widget.MessagingImageMessage) view;
+
+                // If the drawable isn't an AnimatedImageDrawable, we can't set it to animate.
+                Drawable d = imageMessage.getDrawable();
+                if (!(d instanceof AnimatedImageDrawable)) {
+                    continue;
+                }
+                AnimatedImageDrawable animatedImageDrawable = (AnimatedImageDrawable) d;
+                if (running) {
+                    animatedImageDrawable.start();
+                } else {
+                    animatedImageDrawable.stop();
+                }
+            }
+        }
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationViewWrapper.java
index 1c22f09..ff5b9cb 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationViewWrapper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationViewWrapper.java
@@ -403,4 +403,12 @@
         NotificationFadeAware.setLayerTypeForFaded(getIcon(), faded);
         NotificationFadeAware.setLayerTypeForFaded(getExpandButton(), faded);
     }
+
+    /**
+     * Starts or stops the animations in any drawables contained in this Notification.
+     *
+     * @param running Whether the animations should be set to run.
+     */
+    public void setAnimationsRunning(boolean running) {
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
index 356ddfa..4837075 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
@@ -3132,7 +3132,7 @@
     private void updateAnimationState(boolean running, View child) {
         if (child instanceof ExpandableNotificationRow) {
             ExpandableNotificationRow row = (ExpandableNotificationRow) child;
-            row.setIconAnimationRunning(running);
+            row.setAnimationRunning(running);
         }
     }
 
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 8110b47..64c7ff7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
@@ -64,7 +64,6 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
-import android.content.pm.IPackageManager;
 import android.content.pm.PackageManager;
 import android.content.pm.ResolveInfo;
 import android.content.res.Configuration;
@@ -292,24 +291,6 @@
 
     private static final UiEventLogger sUiEventLogger = new UiEventLoggerImpl();
 
-    /**
-     * If true, the system is in the half-boot-to-decryption-screen state.
-     * Prudently disable QS and notifications.
-     */
-    public static final boolean ONLY_CORE_APPS;
-
-    static {
-        boolean onlyCoreApps;
-        try {
-            IPackageManager packageManager =
-                    IPackageManager.Stub.asInterface(ServiceManager.getService("package"));
-            onlyCoreApps = packageManager != null && packageManager.isOnlyCoreApps();
-        } catch (RemoteException e) {
-            onlyCoreApps = false;
-        }
-        ONLY_CORE_APPS = onlyCoreApps;
-    }
-
     private final Context mContext;
     private final LockscreenShadeTransitionController mLockscreenShadeTransitionController;
     private final DeviceStateManager mDeviceStateManager;
@@ -1702,8 +1683,7 @@
                         || !mUserSwitcherController.isSimpleUserSwitcher())
                 && !isShadeDisabled()
                 && ((mDisabled2 & StatusBarManager.DISABLE2_QUICK_SETTINGS) == 0)
-                && !mDozing
-                && !ONLY_CORE_APPS;
+                && !mDozing;
         mNotificationPanelViewController.setQsExpansionEnabledPolicy(expandEnabled);
         Log.d(TAG, "updateQsExpansionEnabled - QS Expand enabled: " + expandEnabled);
     }
diff --git a/packages/SystemUI/src/com/android/systemui/telephony/ui/activity/SwitchToManagedProfileForCallActivity.kt b/packages/SystemUI/src/com/android/systemui/telephony/ui/activity/SwitchToManagedProfileForCallActivity.kt
new file mode 100644
index 0000000..e092f01
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/telephony/ui/activity/SwitchToManagedProfileForCallActivity.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.telephony.ui.activity
+
+import android.app.ActivityOptions
+import android.content.DialogInterface
+import android.content.Intent
+import android.net.Uri
+import android.os.Bundle
+import android.os.UserHandle
+import android.util.Log
+import android.view.WindowManager
+import com.android.internal.app.AlertActivity
+import com.android.systemui.R
+
+/** Dialog shown to the user to switch to managed profile for making a call using work SIM. */
+class SwitchToManagedProfileForCallActivity : AlertActivity(), DialogInterface.OnClickListener {
+    private lateinit var phoneNumber: Uri
+    private var managedProfileUserId = UserHandle.USER_NULL
+
+    override fun onCreate(savedInstanceState: Bundle?) {
+        window.addSystemFlags(
+            WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS
+        )
+        super.onCreate(savedInstanceState)
+
+        phoneNumber = intent.getData()
+        managedProfileUserId =
+            intent.getIntExtra(
+                "android.telecom.extra.MANAGED_PROFILE_USER_ID",
+                UserHandle.USER_NULL
+            )
+
+        mAlertParams.apply {
+            mTitle = getString(R.string.call_from_work_profile_title)
+            mMessage = getString(R.string.call_from_work_profile_text)
+            mPositiveButtonText = getString(R.string.call_from_work_profile_action)
+            mNegativeButtonText = getString(R.string.call_from_work_profile_close)
+            mPositiveButtonListener = this@SwitchToManagedProfileForCallActivity
+            mNegativeButtonListener = this@SwitchToManagedProfileForCallActivity
+        }
+        setupAlert()
+    }
+
+    override fun onClick(dialog: DialogInterface?, which: Int) {
+        if (which == BUTTON_POSITIVE) {
+            switchToManagedProfile()
+        }
+        finish()
+    }
+
+    private fun switchToManagedProfile() {
+        try {
+            applicationContext.startActivityAsUser(
+                Intent(Intent.ACTION_DIAL, phoneNumber),
+                ActivityOptions.makeOpenCrossProfileAppsAnimation().toBundle(),
+                UserHandle.of(managedProfileUserId)
+            )
+        } catch (e: Exception) {
+            Log.e(TAG, "Failed to launch activity", e)
+        }
+    }
+
+    companion object {
+        private const val TAG = "SwitchToManagedProfileForCallActivity"
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/unfold/UnfoldLightRevealOverlayAnimation.kt b/packages/SystemUI/src/com/android/systemui/unfold/UnfoldLightRevealOverlayAnimation.kt
index 523cf68..0069bb5 100644
--- a/packages/SystemUI/src/com/android/systemui/unfold/UnfoldLightRevealOverlayAnimation.kt
+++ b/packages/SystemUI/src/com/android/systemui/unfold/UnfoldLightRevealOverlayAnimation.kt
@@ -36,6 +36,8 @@
 import android.view.WindowManager
 import android.view.WindowlessWindowManager
 import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags
 import com.android.systemui.statusbar.LightRevealEffect
 import com.android.systemui.statusbar.LightRevealScrim
 import com.android.systemui.statusbar.LinearLightRevealEffect
@@ -57,6 +59,7 @@
 @Inject
 constructor(
     private val context: Context,
+    private val featureFlags: FeatureFlags,
     private val deviceStateManager: DeviceStateManager,
     private val contentResolver: ContentResolver,
     private val displayManager: DisplayManager,
@@ -81,6 +84,7 @@
     private var scrimView: LightRevealScrim? = null
     private var isFolded: Boolean = false
     private var isUnfoldHandled: Boolean = true
+    private var overlayAddReason: AddOverlayReason? = null
 
     private var currentRotation: Int = context.display!!.rotation
 
@@ -158,6 +162,8 @@
         ensureInBackground()
         ensureOverlayRemoved()
 
+        overlayAddReason = reason
+
         val newRoot = SurfaceControlViewHost(context, context.display!!, wwm)
         val params = getLayoutParams()
         val newView =
@@ -170,11 +176,7 @@
                 .apply {
                     revealEffect = createLightRevealEffect()
                     isScrimOpaqueChangedListener = Consumer {}
-                    revealAmount =
-                        when (reason) {
-                            FOLD -> TRANSPARENT
-                            UNFOLD -> BLACK
-                        }
+                    revealAmount = calculateRevealAmount()
                 }
 
         newRoot.setView(newView, params)
@@ -207,6 +209,31 @@
         root = newRoot
     }
 
+    private fun calculateRevealAmount(animationProgress: Float? = null): Float {
+        val overlayAddReason = overlayAddReason ?: UNFOLD
+
+        if (animationProgress == null) {
+            // Animation progress is unknown, calculate the initial value based on the overlay
+            // add reason
+            return when (overlayAddReason) {
+                FOLD -> TRANSPARENT
+                UNFOLD -> BLACK
+            }
+        }
+
+        val showVignetteWhenFolding =
+            featureFlags.isEnabled(Flags.ENABLE_DARK_VIGNETTE_WHEN_FOLDING)
+
+        return if (!showVignetteWhenFolding && overlayAddReason == FOLD) {
+            // Do not darken the content when SHOW_VIGNETTE_WHEN_FOLDING flag is off
+            // and we are folding the device. We still add the overlay to block touches
+            // while the animation is running but the overlay is transparent.
+            TRANSPARENT
+        } else {
+            animationProgress
+        }
+    }
+
     private fun getLayoutParams(): WindowManager.LayoutParams {
         val params: WindowManager.LayoutParams = WindowManager.LayoutParams()
 
@@ -259,7 +286,7 @@
     private inner class TransitionListener : TransitionProgressListener {
 
         override fun onTransitionProgress(progress: Float) {
-            executeInBackground { scrimView?.revealAmount = progress }
+            executeInBackground { scrimView?.revealAmount = calculateRevealAmount(progress) }
         }
 
         override fun onTransitionFinished() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/SideFpsControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/SideFpsControllerTest.kt
index 3c40835..b92c5d0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/SideFpsControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/SideFpsControllerTest.kt
@@ -53,6 +53,7 @@
 import com.airbnb.lottie.LottieAnimationView
 import com.android.systemui.R
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.SysuiTestableContext
 import com.android.systemui.dump.DumpManager
 import com.android.systemui.recents.OverviewProxyService
 import com.android.systemui.util.concurrency.FakeExecutor
@@ -106,8 +107,7 @@
 
     enum class DeviceConfig {
         X_ALIGNED,
-        Y_ALIGNED_UNFOLDED,
-        Y_ALIGNED_FOLDED
+        Y_ALIGNED,
     }
 
     private lateinit var deviceConfig: DeviceConfig
@@ -143,6 +143,7 @@
 
     private fun testWithDisplay(
         deviceConfig: DeviceConfig = DeviceConfig.X_ALIGNED,
+        isReverseDefaultRotation: Boolean = false,
         initInfo: DisplayInfo.() -> Unit = {},
         windowInsets: WindowInsets = insetsForSmallNavbar(),
         block: () -> Unit
@@ -151,27 +152,21 @@
 
         when (deviceConfig) {
             DeviceConfig.X_ALIGNED -> {
-                displayWidth = 2560
-                displayHeight = 1600
-                sensorLocation = SensorLocationInternal("", 2325, 0, 0)
-                boundsWidth = 160
-                boundsHeight = 84
+                displayWidth = 3000
+                displayHeight = 1500
+                sensorLocation = SensorLocationInternal("", 2500, 0, 0)
+                boundsWidth = 200
+                boundsHeight = 100
             }
-            DeviceConfig.Y_ALIGNED_UNFOLDED -> {
-                displayWidth = 2208
-                displayHeight = 1840
-                sensorLocation = SensorLocationInternal("", 0, 510, 0)
-                boundsWidth = 110
-                boundsHeight = 210
-            }
-            DeviceConfig.Y_ALIGNED_FOLDED -> {
-                displayWidth = 1080
-                displayHeight = 2100
-                sensorLocation = SensorLocationInternal("", 0, 590, 0)
-                boundsWidth = 110
-                boundsHeight = 210
+            DeviceConfig.Y_ALIGNED -> {
+                displayWidth = 2500
+                displayHeight = 2000
+                sensorLocation = SensorLocationInternal("", 0, 300, 0)
+                boundsWidth = 100
+                boundsHeight = 200
             }
         }
+
         indicatorBounds = Rect(0, 0, boundsWidth, boundsHeight)
         displayBounds = Rect(0, 0, displayWidth, displayHeight)
         var locations = listOf(sensorLocation)
@@ -194,8 +189,10 @@
 
         val displayInfo = DisplayInfo()
         displayInfo.initInfo()
+
         val dmGlobal = mock(DisplayManagerGlobal::class.java)
         val display = Display(dmGlobal, DISPLAY_ID, displayInfo, DEFAULT_DISPLAY_ADJUSTMENTS)
+
         whenEver(dmGlobal.getDisplayInfo(eq(DISPLAY_ID))).thenReturn(displayInfo)
         whenEver(windowManager.defaultDisplay).thenReturn(display)
         whenEver(windowManager.maximumWindowMetrics)
@@ -203,9 +200,15 @@
         whenEver(windowManager.currentWindowMetrics)
             .thenReturn(WindowMetrics(displayBounds, windowInsets))
 
+        val sideFpsControllerContext = context.createDisplayContext(display) as SysuiTestableContext
+        sideFpsControllerContext.orCreateTestableResources.addOverride(
+            com.android.internal.R.bool.config_reverseDefaultRotation,
+            isReverseDefaultRotation
+        )
+
         sideFpsController =
             SideFpsController(
-                context.createDisplayContext(display),
+                sideFpsControllerContext,
                 layoutInflater,
                 fingerprintManager,
                 windowManager,
@@ -299,108 +302,316 @@
     }
 
     @Test
-    fun showsWithTaskbar() =
-        testWithDisplay(deviceConfig = DeviceConfig.X_ALIGNED, { rotation = Surface.ROTATION_0 }) {
-            hidesWithTaskbar(visible = true)
-        }
-
-    @Test
-    fun showsWithTaskbarOnY() =
+    fun showsSfpsIndicatorWithTaskbarForXAlignedSensor_0() =
         testWithDisplay(
-            deviceConfig = DeviceConfig.Y_ALIGNED_UNFOLDED,
+            deviceConfig = DeviceConfig.X_ALIGNED,
+            isReverseDefaultRotation = false,
             { rotation = Surface.ROTATION_0 }
-        ) { hidesWithTaskbar(visible = true) }
+        ) { verifySfpsIndicatorVisibilityOnTaskbarUpdate(sfpsViewVisible = true) }
 
     @Test
-    fun showsWithTaskbar90() =
-        testWithDisplay(deviceConfig = DeviceConfig.X_ALIGNED, { rotation = Surface.ROTATION_90 }) {
-            hidesWithTaskbar(visible = true)
+    fun showsSfpsIndicatorWithTaskbarForXAlignedSensor_90() =
+        testWithDisplay(
+            deviceConfig = DeviceConfig.X_ALIGNED,
+            isReverseDefaultRotation = false,
+            { rotation = Surface.ROTATION_90 }
+        ) { verifySfpsIndicatorVisibilityOnTaskbarUpdate(sfpsViewVisible = true) }
+
+    @Test
+    fun showsSfpsIndicatorWithTaskbarForXAlignedSensor_180() =
+        testWithDisplay(
+            deviceConfig = DeviceConfig.X_ALIGNED,
+            isReverseDefaultRotation = false,
+            { rotation = Surface.ROTATION_180 }
+        ) { verifySfpsIndicatorVisibilityOnTaskbarUpdate(sfpsViewVisible = true) }
+
+    @Test
+    fun showsSfpsIndicatorWithTaskbarCollapsedDownForXAlignedSensor_180() =
+        testWithDisplay(
+            deviceConfig = DeviceConfig.X_ALIGNED,
+            isReverseDefaultRotation = false,
+            { rotation = Surface.ROTATION_180 },
+            windowInsets = insetsForSmallNavbar()
+        ) { verifySfpsIndicatorVisibilityOnTaskbarUpdate(sfpsViewVisible = true) }
+
+    @Test
+    fun hidesSfpsIndicatorWhenOccludingTaskbarForXAlignedSensor_180() =
+        testWithDisplay(
+            deviceConfig = DeviceConfig.X_ALIGNED,
+            isReverseDefaultRotation = false,
+            { rotation = Surface.ROTATION_180 },
+            windowInsets = insetsForLargeNavbar()
+        ) { verifySfpsIndicatorVisibilityOnTaskbarUpdate(sfpsViewVisible = false) }
+
+    @Test
+    fun showsSfpsIndicatorWithTaskbarForXAlignedSensor_270() =
+        testWithDisplay(
+            deviceConfig = DeviceConfig.X_ALIGNED,
+            isReverseDefaultRotation = false,
+            { rotation = Surface.ROTATION_270 }
+        ) { verifySfpsIndicatorVisibilityOnTaskbarUpdate(sfpsViewVisible = true) }
+
+    @Test
+    fun showsSfpsIndicatorWithTaskbarForXAlignedSensor_InReverseDefaultRotation_0() =
+        testWithDisplay(
+            deviceConfig = DeviceConfig.X_ALIGNED,
+            isReverseDefaultRotation = true,
+            { rotation = Surface.ROTATION_0 }
+        ) { verifySfpsIndicatorVisibilityOnTaskbarUpdate(sfpsViewVisible = true) }
+
+    @Test
+    fun showsSfpsIndicatorWithTaskbarForXAlignedSensor_InReverseDefaultRotation_90() =
+        testWithDisplay(
+            deviceConfig = DeviceConfig.X_ALIGNED,
+            isReverseDefaultRotation = true,
+            { rotation = Surface.ROTATION_90 }
+        ) { verifySfpsIndicatorVisibilityOnTaskbarUpdate(sfpsViewVisible = true) }
+
+    @Test
+    fun showsSfpsIndicatorWithTaskbarCollapsedDownForXAlignedSensor_InReverseDefaultRotation_90() =
+        testWithDisplay(
+            deviceConfig = DeviceConfig.X_ALIGNED,
+            isReverseDefaultRotation = true,
+            { rotation = Surface.ROTATION_90 },
+            windowInsets = insetsForSmallNavbar()
+        ) { verifySfpsIndicatorVisibilityOnTaskbarUpdate(sfpsViewVisible = true) }
+
+    @Test
+    fun hidesSfpsIndicatorWhenOccludingTaskbarForXAlignedSensor_InReverseDefaultRotation_90() =
+        testWithDisplay(
+            deviceConfig = DeviceConfig.X_ALIGNED,
+            isReverseDefaultRotation = true,
+            { rotation = Surface.ROTATION_90 },
+            windowInsets = insetsForLargeNavbar()
+        ) { verifySfpsIndicatorVisibilityOnTaskbarUpdate(sfpsViewVisible = false) }
+
+    @Test
+    fun showsSfpsIndicatorWithTaskbarForXAlignedSensor_InReverseDefaultRotation_180() =
+        testWithDisplay(
+            deviceConfig = DeviceConfig.X_ALIGNED,
+            isReverseDefaultRotation = true,
+            { rotation = Surface.ROTATION_180 }
+        ) { verifySfpsIndicatorVisibilityOnTaskbarUpdate(sfpsViewVisible = true) }
+
+    @Test
+    fun showsSfpsIndicatorWithTaskbarForXAlignedSensor_InReverseDefaultRotation_270() =
+        testWithDisplay(
+            deviceConfig = DeviceConfig.X_ALIGNED,
+            isReverseDefaultRotation = true,
+            { rotation = Surface.ROTATION_270 }
+        ) { verifySfpsIndicatorVisibilityOnTaskbarUpdate(sfpsViewVisible = true) }
+
+    @Test
+    fun showsSfpsIndicatorWithTaskbarForYAlignedSensor_0() =
+        testWithDisplay(
+            deviceConfig = DeviceConfig.Y_ALIGNED,
+            isReverseDefaultRotation = false,
+            { rotation = Surface.ROTATION_0 }
+        ) { verifySfpsIndicatorVisibilityOnTaskbarUpdate(sfpsViewVisible = true) }
+
+    @Test
+    fun showsSfpsIndicatorWithTaskbarForYAlignedSensor_90() =
+        testWithDisplay(
+            deviceConfig = DeviceConfig.Y_ALIGNED,
+            isReverseDefaultRotation = false,
+            { rotation = Surface.ROTATION_90 }
+        ) { verifySfpsIndicatorVisibilityOnTaskbarUpdate(sfpsViewVisible = true) }
+
+    @Test
+    fun showsSfpsIndicatorWithTaskbarForYAlignedSensor_180() =
+        testWithDisplay(
+            deviceConfig = DeviceConfig.Y_ALIGNED,
+            isReverseDefaultRotation = false,
+            { rotation = Surface.ROTATION_180 },
+        ) {
+            verifySfpsIndicatorVisibilityOnTaskbarUpdate(sfpsViewVisible = true)
         }
 
     @Test
-    fun showsWithTaskbar90OnY() =
+    fun showsSfpsIndicatorWithTaskbarForYAlignedSensor_270() =
         testWithDisplay(
-            deviceConfig = DeviceConfig.Y_ALIGNED_UNFOLDED,
-            { rotation = Surface.ROTATION_90 }
-        ) { hidesWithTaskbar(visible = true) }
-
-    @Test
-    fun showsWithTaskbar180() =
-        testWithDisplay(
-            deviceConfig = DeviceConfig.X_ALIGNED,
-            { rotation = Surface.ROTATION_180 }
-        ) { hidesWithTaskbar(visible = true) }
-
-    @Test
-    fun showsWithTaskbar270OnY() =
-        testWithDisplay(
-            deviceConfig = DeviceConfig.Y_ALIGNED_UNFOLDED,
+            deviceConfig = DeviceConfig.Y_ALIGNED,
+            isReverseDefaultRotation = false,
             { rotation = Surface.ROTATION_270 }
-        ) { hidesWithTaskbar(visible = true) }
+        ) { verifySfpsIndicatorVisibilityOnTaskbarUpdate(sfpsViewVisible = true) }
 
     @Test
-    fun showsWithTaskbarCollapsedDown() =
+    fun showsSfpsIndicatorWithTaskbarCollapsedDownForYAlignedSensor_270() =
         testWithDisplay(
-            deviceConfig = DeviceConfig.X_ALIGNED,
+            deviceConfig = DeviceConfig.Y_ALIGNED,
+            isReverseDefaultRotation = false,
             { rotation = Surface.ROTATION_270 },
             windowInsets = insetsForSmallNavbar()
-        ) { hidesWithTaskbar(visible = true) }
+        ) { verifySfpsIndicatorVisibilityOnTaskbarUpdate(sfpsViewVisible = true) }
 
     @Test
-    fun showsWithTaskbarCollapsedDownOnY() =
+    fun hidesSfpsIndicatorWhenOccludingTaskbarForYAlignedSensor_270() =
         testWithDisplay(
-            deviceConfig = DeviceConfig.Y_ALIGNED_UNFOLDED,
-            { rotation = Surface.ROTATION_180 },
-            windowInsets = insetsForSmallNavbar()
-        ) { hidesWithTaskbar(visible = true) }
-
-    @Test
-    fun hidesWithTaskbarDown() =
-        testWithDisplay(
-            deviceConfig = DeviceConfig.X_ALIGNED,
-            { rotation = Surface.ROTATION_180 },
-            windowInsets = insetsForLargeNavbar()
-        ) { hidesWithTaskbar(visible = false) }
-
-    @Test
-    fun hidesWithTaskbarDownOnY() =
-        testWithDisplay(
-            deviceConfig = DeviceConfig.Y_ALIGNED_UNFOLDED,
+            deviceConfig = DeviceConfig.Y_ALIGNED,
+            isReverseDefaultRotation = false,
             { rotation = Surface.ROTATION_270 },
             windowInsets = insetsForLargeNavbar()
-        ) { hidesWithTaskbar(visible = true) }
+        ) { verifySfpsIndicatorVisibilityOnTaskbarUpdate(sfpsViewVisible = false) }
 
-    private fun hidesWithTaskbar(visible: Boolean) {
+    @Test
+    fun showsSfpsIndicatorWithTaskbarForYAlignedSensor_InReverseDefaultRotation_0() =
+        testWithDisplay(
+            deviceConfig = DeviceConfig.Y_ALIGNED,
+            isReverseDefaultRotation = true,
+            { rotation = Surface.ROTATION_0 }
+        ) { verifySfpsIndicatorVisibilityOnTaskbarUpdate(sfpsViewVisible = true) }
+
+    @Test
+    fun showsSfpsIndicatorWithTaskbarForYAlignedSensor_InReverseDefaultRotation_90() =
+        testWithDisplay(
+            deviceConfig = DeviceConfig.Y_ALIGNED,
+            isReverseDefaultRotation = true,
+            { rotation = Surface.ROTATION_90 },
+        ) {
+            verifySfpsIndicatorVisibilityOnTaskbarUpdate(sfpsViewVisible = true)
+        }
+
+    @Test
+    fun showsSfpsIndicatorWithTaskbarForYAlignedSensor_InReverseDefaultRotation_180() =
+        testWithDisplay(
+            deviceConfig = DeviceConfig.Y_ALIGNED,
+            isReverseDefaultRotation = true,
+            { rotation = Surface.ROTATION_180 }
+        ) { verifySfpsIndicatorVisibilityOnTaskbarUpdate(sfpsViewVisible = true) }
+
+    @Test
+    fun showsSfpsIndicatorWithTaskbarCollapsedDownForYAlignedSensor_InReverseDefaultRotation_180() =
+        testWithDisplay(
+            deviceConfig = DeviceConfig.Y_ALIGNED,
+            isReverseDefaultRotation = true,
+            { rotation = Surface.ROTATION_180 },
+            windowInsets = insetsForSmallNavbar()
+        ) { verifySfpsIndicatorVisibilityOnTaskbarUpdate(sfpsViewVisible = true) }
+
+    @Test
+    fun hidesSfpsIndicatorWhenOccludingTaskbarForYAlignedSensor_InReverseDefaultRotation_180() =
+        testWithDisplay(
+            deviceConfig = DeviceConfig.Y_ALIGNED,
+            isReverseDefaultRotation = true,
+            { rotation = Surface.ROTATION_180 },
+            windowInsets = insetsForLargeNavbar()
+        ) { verifySfpsIndicatorVisibilityOnTaskbarUpdate(sfpsViewVisible = false) }
+
+    @Test
+    fun showsSfpsIndicatorWithTaskbarForYAlignedSensor_InReverseDefaultRotation_270() =
+        testWithDisplay(
+            deviceConfig = DeviceConfig.Y_ALIGNED,
+            isReverseDefaultRotation = true,
+            { rotation = Surface.ROTATION_270 }
+        ) { verifySfpsIndicatorVisibilityOnTaskbarUpdate(sfpsViewVisible = true) }
+
+    private fun verifySfpsIndicatorVisibilityOnTaskbarUpdate(sfpsViewVisible: Boolean) {
+        sideFpsController.overlayOffsets = sensorLocation
         overlayController.show(SENSOR_ID, REASON_UNKNOWN)
         executor.runAllReady()
 
-        sideFpsController.overviewProxyListener.onTaskbarStatusUpdated(visible, false)
+        sideFpsController.overviewProxyListener.onTaskbarStatusUpdated(true, false)
         executor.runAllReady()
 
         verify(windowManager).addView(any(), any())
         verify(windowManager, never()).removeView(any())
-        verify(sideFpsView).visibility = if (visible) View.VISIBLE else View.GONE
+        verify(sideFpsView).visibility = if (sfpsViewVisible) View.VISIBLE else View.GONE
     }
 
+    /**
+     * {@link SideFpsController#updateOverlayParams} calculates indicator placement for ROTATION_0,
+     * and uses RotateUtils.rotateBounds to map to the correct indicator location given the device
+     * rotation. Assuming RotationUtils.rotateBounds works correctly, tests for indicator placement
+     * in other rotations have been omitted.
+     */
     @Test
-    fun testIndicatorPlacementForXAlignedSensor() =
-        testWithDisplay(deviceConfig = DeviceConfig.X_ALIGNED) {
-            overlayController.show(SENSOR_ID, REASON_UNKNOWN)
+    fun verifiesIndicatorPlacementForXAlignedSensor_0() {
+        testWithDisplay(
+            deviceConfig = DeviceConfig.X_ALIGNED,
+            isReverseDefaultRotation = false,
+            { rotation = Surface.ROTATION_0 }
+        ) {
             sideFpsController.overlayOffsets = sensorLocation
+
             sideFpsController.updateOverlayParams(windowManager.defaultDisplay, indicatorBounds)
+
+            overlayController.show(SENSOR_ID, REASON_UNKNOWN)
             executor.runAllReady()
 
             verify(windowManager).updateViewLayout(any(), overlayViewParamsCaptor.capture())
-
             assertThat(overlayViewParamsCaptor.value.x).isEqualTo(sensorLocation.sensorLocationX)
             assertThat(overlayViewParamsCaptor.value.y).isEqualTo(0)
         }
+    }
 
+    /**
+     * {@link SideFpsController#updateOverlayParams} calculates indicator placement for ROTATION_270
+     * in reverse default rotation. It then uses RotateUtils.rotateBounds to map to the correct
+     * indicator location given the device rotation. Assuming RotationUtils.rotateBounds works
+     * correctly, tests for indicator placement in other rotations have been omitted.
+     */
     @Test
-    fun testIndicatorPlacementForYAlignedSensor() =
-        testWithDisplay(deviceConfig = DeviceConfig.Y_ALIGNED_UNFOLDED) {
+    fun verifiesIndicatorPlacementForXAlignedSensor_InReverseDefaultRotation_270() {
+        testWithDisplay(
+            deviceConfig = DeviceConfig.X_ALIGNED,
+            isReverseDefaultRotation = true,
+            { rotation = Surface.ROTATION_270 }
+        ) {
             sideFpsController.overlayOffsets = sensorLocation
+
             sideFpsController.updateOverlayParams(windowManager.defaultDisplay, indicatorBounds)
+
+            overlayController.show(SENSOR_ID, REASON_UNKNOWN)
+            executor.runAllReady()
+
+            verify(windowManager).updateViewLayout(any(), overlayViewParamsCaptor.capture())
+            assertThat(overlayViewParamsCaptor.value.x).isEqualTo(sensorLocation.sensorLocationX)
+            assertThat(overlayViewParamsCaptor.value.y).isEqualTo(0)
+        }
+    }
+
+    /**
+     * {@link SideFpsController#updateOverlayParams} calculates indicator placement for ROTATION_0,
+     * and uses RotateUtils.rotateBounds to map to the correct indicator location given the device
+     * rotation. Assuming RotationUtils.rotateBounds works correctly, tests for indicator placement
+     * in other rotations have been omitted.
+     */
+    @Test
+    fun verifiesIndicatorPlacementForYAlignedSensor_0() =
+        testWithDisplay(
+            deviceConfig = DeviceConfig.Y_ALIGNED,
+            isReverseDefaultRotation = false,
+            { rotation = Surface.ROTATION_0 }
+        ) {
+            sideFpsController.overlayOffsets = sensorLocation
+
+            sideFpsController.updateOverlayParams(windowManager.defaultDisplay, indicatorBounds)
+
+            overlayController.show(SENSOR_ID, REASON_UNKNOWN)
+            executor.runAllReady()
+
+            verify(windowManager).updateViewLayout(any(), overlayViewParamsCaptor.capture())
+            assertThat(overlayViewParamsCaptor.value.x).isEqualTo(displayWidth - boundsWidth)
+            assertThat(overlayViewParamsCaptor.value.y).isEqualTo(sensorLocation.sensorLocationY)
+        }
+
+    /**
+     * {@link SideFpsController#updateOverlayParams} calculates indicator placement for ROTATION_270
+     * in reverse default rotation. It then uses RotateUtils.rotateBounds to map to the correct
+     * indicator location given the device rotation. Assuming RotationUtils.rotateBounds works
+     * correctly, tests for indicator placement in other rotations have been omitted.
+     */
+    @Test
+    fun verifiesIndicatorPlacementForYAlignedSensor_InReverseDefaultRotation_270() =
+        testWithDisplay(
+            deviceConfig = DeviceConfig.Y_ALIGNED,
+            isReverseDefaultRotation = true,
+            { rotation = Surface.ROTATION_270 }
+        ) {
+            sideFpsController.overlayOffsets = sensorLocation
+
+            sideFpsController.updateOverlayParams(windowManager.defaultDisplay, indicatorBounds)
+
             overlayController.show(SENSOR_ID, REASON_UNKNOWN)
             executor.runAllReady()
 
@@ -412,7 +623,6 @@
     @Test
     fun hasSideFpsSensor_withSensorProps_returnsTrue() = testWithDisplay {
         // By default all those tests assume the side fps sensor is available.
-
         assertThat(fingerprintManager.hasSideFpsSensor()).isTrue()
     }
 
@@ -425,7 +635,7 @@
 
     @Test
     fun testLayoutParams_isKeyguardDialogType() =
-        testWithDisplay(deviceConfig = DeviceConfig.Y_ALIGNED_UNFOLDED) {
+        testWithDisplay(deviceConfig = DeviceConfig.Y_ALIGNED) {
             sideFpsController.overlayOffsets = sensorLocation
             sideFpsController.updateOverlayParams(windowManager.defaultDisplay, indicatorBounds)
             overlayController.show(SENSOR_ID, REASON_UNKNOWN)
@@ -440,7 +650,7 @@
 
     @Test
     fun testLayoutParams_hasNoMoveAnimationWindowFlag() =
-        testWithDisplay(deviceConfig = DeviceConfig.Y_ALIGNED_UNFOLDED) {
+        testWithDisplay(deviceConfig = DeviceConfig.Y_ALIGNED) {
             sideFpsController.overlayOffsets = sensorLocation
             sideFpsController.updateOverlayParams(windowManager.defaultDisplay, indicatorBounds)
             overlayController.show(SENSOR_ID, REASON_UNKNOWN)
@@ -455,7 +665,7 @@
 
     @Test
     fun testLayoutParams_hasTrustedOverlayWindowFlag() =
-        testWithDisplay(deviceConfig = DeviceConfig.Y_ALIGNED_UNFOLDED) {
+        testWithDisplay(deviceConfig = DeviceConfig.Y_ALIGNED) {
             sideFpsController.overlayOffsets = sensorLocation
             sideFpsController.updateOverlayParams(windowManager.defaultDisplay, indicatorBounds)
             overlayController.show(SENSOR_ID, REASON_UNKNOWN)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/TrustRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/TrustRepositoryTest.kt
new file mode 100644
index 0000000..4b06905
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/TrustRepositoryTest.kt
@@ -0,0 +1,163 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.data.repository
+
+import android.app.trust.TrustManager
+import android.content.pm.UserInfo
+import androidx.test.filters.SmallTest
+import com.android.keyguard.logging.TrustRepositoryLogger
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.plugins.log.LogBuffer
+import com.android.systemui.plugins.log.LogcatEchoTracker
+import com.android.systemui.user.data.repository.FakeUserRepository
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import org.mockito.ArgumentCaptor
+import org.mockito.Captor
+import org.mockito.Mock
+import org.mockito.Mockito.mock
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(JUnit4::class)
+class TrustRepositoryTest : SysuiTestCase() {
+    @Mock private lateinit var trustManager: TrustManager
+    @Captor private lateinit var listenerCaptor: ArgumentCaptor<TrustManager.TrustListener>
+    private lateinit var userRepository: FakeUserRepository
+    private lateinit var testScope: TestScope
+    private val users = listOf(UserInfo(1, "user 1", 0), UserInfo(2, "user 1", 0))
+
+    private lateinit var underTest: TrustRepository
+
+    @Before
+    fun setUp() {
+        MockitoAnnotations.initMocks(this)
+        testScope = TestScope()
+        userRepository = FakeUserRepository()
+        userRepository.setUserInfos(users)
+
+        val logger =
+            TrustRepositoryLogger(
+                LogBuffer("TestBuffer", 1, mock(LogcatEchoTracker::class.java), false)
+            )
+        underTest =
+            TrustRepositoryImpl(testScope.backgroundScope, userRepository, trustManager, logger)
+    }
+
+    @Test
+    fun isCurrentUserTrusted_whenTrustChanges_emitsLatestValue() =
+        testScope.runTest {
+            runCurrent()
+            verify(trustManager).registerTrustListener(listenerCaptor.capture())
+            val listener = listenerCaptor.value
+
+            val currentUserId = users[0].id
+            userRepository.setSelectedUserInfo(users[0])
+            val isCurrentUserTrusted = collectLastValue(underTest.isCurrentUserTrusted)
+
+            listener.onTrustChanged(true, false, currentUserId, 0, emptyList())
+            assertThat(isCurrentUserTrusted()).isTrue()
+
+            listener.onTrustChanged(false, false, currentUserId, 0, emptyList())
+
+            assertThat(isCurrentUserTrusted()).isFalse()
+        }
+
+    @Test
+    fun isCurrentUserTrusted_isFalse_byDefault() =
+        testScope.runTest {
+            runCurrent()
+
+            val isCurrentUserTrusted = collectLastValue(underTest.isCurrentUserTrusted)
+
+            assertThat(isCurrentUserTrusted()).isFalse()
+        }
+
+    @Test
+    fun isCurrentUserTrusted_whenTrustChangesForDifferentUser_noop() =
+        testScope.runTest {
+            runCurrent()
+            verify(trustManager).registerTrustListener(listenerCaptor.capture())
+            userRepository.setSelectedUserInfo(users[0])
+            val listener = listenerCaptor.value
+
+            val isCurrentUserTrusted = collectLastValue(underTest.isCurrentUserTrusted)
+            // current user is trusted.
+            listener.onTrustChanged(true, true, users[0].id, 0, emptyList())
+            // some other user is not trusted.
+            listener.onTrustChanged(false, false, users[1].id, 0, emptyList())
+
+            assertThat(isCurrentUserTrusted()).isTrue()
+        }
+
+    @Test
+    fun isCurrentUserTrusted_whenTrustChangesForCurrentUser_emitsNewValue() =
+        testScope.runTest {
+            runCurrent()
+            verify(trustManager).registerTrustListener(listenerCaptor.capture())
+            val listener = listenerCaptor.value
+            userRepository.setSelectedUserInfo(users[0])
+
+            val isCurrentUserTrusted = collectLastValue(underTest.isCurrentUserTrusted)
+            listener.onTrustChanged(true, true, users[0].id, 0, emptyList())
+            assertThat(isCurrentUserTrusted()).isTrue()
+
+            listener.onTrustChanged(false, true, users[0].id, 0, emptyList())
+            assertThat(isCurrentUserTrusted()).isFalse()
+        }
+
+    @Test
+    fun isCurrentUserTrusted_whenUserChangesWithoutRecentTrustChange_defaultsToFalse() =
+        testScope.runTest {
+            runCurrent()
+            verify(trustManager).registerTrustListener(listenerCaptor.capture())
+            val listener = listenerCaptor.value
+            userRepository.setSelectedUserInfo(users[0])
+            listener.onTrustChanged(true, true, users[0].id, 0, emptyList())
+
+            val isCurrentUserTrusted = collectLastValue(underTest.isCurrentUserTrusted)
+            userRepository.setSelectedUserInfo(users[1])
+
+            assertThat(isCurrentUserTrusted()).isFalse()
+        }
+
+    @Test
+    fun isCurrentUserTrusted_trustChangesFirstBeforeUserInfoChanges_emitsCorrectValue() =
+        testScope.runTest {
+            runCurrent()
+            verify(trustManager).registerTrustListener(listenerCaptor.capture())
+            val listener = listenerCaptor.value
+            val isCurrentUserTrusted = collectLastValue(underTest.isCurrentUserTrusted)
+
+            listener.onTrustChanged(true, true, users[0].id, 0, emptyList())
+            assertThat(isCurrentUserTrusted()).isFalse()
+
+            userRepository.setSelectedUserInfo(users[0])
+
+            assertThat(isCurrentUserTrusted()).isTrue()
+        }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt
index a1b6d47..5a7a3d4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt
@@ -17,6 +17,7 @@
 package com.android.systemui.keyguard.domain.interactor
 
 import android.animation.ValueAnimator
+import androidx.test.filters.FlakyTest
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.animation.Interpolators
@@ -56,6 +57,7 @@
  */
 @SmallTest
 @RunWith(JUnit4::class)
+@FlakyTest(bugId = 265303901)
 class KeyguardTransitionScenariosTest : SysuiTestCase() {
     private lateinit var testScope: TestScope
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/CombinedShadeHeaderConstraintsTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/CombinedShadeHeaderConstraintsTest.kt
index f802a5e..ed9baf5b1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/CombinedShadeHeaderConstraintsTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/CombinedShadeHeaderConstraintsTest.kt
@@ -109,11 +109,12 @@
     @Test
     fun testEdgeElementsAlignedWithEdge_largeScreen() {
         with(largeScreenConstraint) {
-            assertThat(getConstraint(R.id.clock).layout.startToStart).isEqualTo(PARENT_ID)
-            assertThat(getConstraint(R.id.clock).layout.horizontalBias).isEqualTo(0f)
+            assertThat(getConstraint(R.id.clock).layout.startToEnd).isEqualTo(R.id.begin_guide)
+            assertThat(getConstraint(R.id.clock).layout.horizontalBias).isEqualTo(0.5f)
 
-            assertThat(getConstraint(R.id.privacy_container).layout.endToEnd).isEqualTo(PARENT_ID)
-            assertThat(getConstraint(R.id.privacy_container).layout.horizontalBias).isEqualTo(1f)
+            assertThat(getConstraint(R.id.privacy_container).layout.endToStart)
+                .isEqualTo(R.id.end_guide)
+            assertThat(getConstraint(R.id.privacy_container).layout.horizontalBias).isEqualTo(0.5f)
         }
     }
 
@@ -219,7 +220,12 @@
                 .isEqualTo(cutoutEnd - padding)
         }
 
-        assertThat(changes.largeScreenConstraintsChanges).isNull()
+        with(largeScreenConstraint) {
+            assertThat(getConstraint(R.id.begin_guide).layout.guideBegin)
+                .isEqualTo(cutoutStart - padding)
+            assertThat(getConstraint(R.id.end_guide).layout.guideEnd)
+                .isEqualTo(cutoutEnd - padding)
+        }
     }
 
     @Test
@@ -246,7 +252,10 @@
             assertThat(getConstraint(R.id.end_guide).layout.guideEnd).isEqualTo(0)
         }
 
-        assertThat(changes.largeScreenConstraintsChanges).isNull()
+        with(largeScreenConstraint) {
+            assertThat(getConstraint(R.id.begin_guide).layout.guideBegin).isEqualTo(0)
+            assertThat(getConstraint(R.id.end_guide).layout.guideEnd).isEqualTo(0)
+        }
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java
index 9d531a1..4559a23 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java
@@ -44,16 +44,23 @@
 import android.app.Notification;
 import android.app.NotificationChannel;
 import android.graphics.Color;
+import android.graphics.drawable.AnimatedVectorDrawable;
+import android.graphics.drawable.AnimationDrawable;
+import android.graphics.drawable.Drawable;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
 import android.testing.TestableLooper.RunWithLooper;
 import android.util.DisplayMetrics;
 import android.view.View;
+import android.widget.ImageView;
 
 import androidx.test.filters.SmallTest;
 
 import com.android.internal.R;
+import com.android.internal.widget.CachingIconView;
 import com.android.systemui.SysuiTestCase;
+import com.android.systemui.flags.FakeFeatureFlags;
+import com.android.systemui.flags.Flags;
 import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.statusbar.notification.AboveShelfChangedListener;
@@ -61,6 +68,7 @@
 import com.android.systemui.statusbar.notification.SourceType;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.row.ExpandableView.OnHeightChangedListener;
+import com.android.systemui.statusbar.notification.row.wrapper.NotificationViewWrapper;
 import com.android.systemui.statusbar.notification.stack.NotificationChildrenContainer;
 
 import org.junit.Assert;
@@ -72,6 +80,7 @@
 import org.mockito.junit.MockitoJUnit;
 import org.mockito.junit.MockitoRule;
 
+import java.util.Arrays;
 import java.util.List;
 
 @SmallTest
@@ -96,6 +105,9 @@
                 mDependency,
                 TestableLooper.get(this));
         mNotificationTestHelper.setDefaultInflationFlags(FLAG_CONTENT_VIEW_ALL);
+        FakeFeatureFlags fakeFeatureFlags = new FakeFeatureFlags();
+        fakeFeatureFlags.set(Flags.NOTIFICATION_ANIMATE_BIG_PICTURE, true);
+        mNotificationTestHelper.setFeatureFlags(fakeFeatureFlags);
         // create a standard private notification row
         Notification normalNotif = mNotificationTestHelper.createNotification();
         normalNotif.publicVersion = null;
@@ -559,4 +571,123 @@
         Assert.assertEquals(1f, mGroupRow.getBottomRoundness(), 0.001f);
         Assert.assertEquals(1f, mGroupRow.getChildrenContainer().getBottomRoundness(), 0.001f);
     }
+
+    @Test
+    public void testSetContentAnimationRunning_Run() throws Exception {
+        // Create views for the notification row.
+        NotificationContentView publicLayout = mock(NotificationContentView.class);
+        mNotifRow.setPublicLayout(publicLayout);
+        NotificationContentView privateLayout = mock(NotificationContentView.class);
+        mNotifRow.setPrivateLayout(privateLayout);
+
+        mNotifRow.setAnimationRunning(true);
+        verify(publicLayout, times(1)).setContentAnimationRunning(true);
+        verify(privateLayout, times(1)).setContentAnimationRunning(true);
+    }
+
+    @Test
+    public void testSetContentAnimationRunning_Stop() {
+        // Create views for the notification row.
+        NotificationContentView publicLayout = mock(NotificationContentView.class);
+        mNotifRow.setPublicLayout(publicLayout);
+        NotificationContentView privateLayout = mock(NotificationContentView.class);
+        mNotifRow.setPrivateLayout(privateLayout);
+
+        mNotifRow.setAnimationRunning(false);
+        verify(publicLayout, times(1)).setContentAnimationRunning(false);
+        verify(privateLayout, times(1)).setContentAnimationRunning(false);
+    }
+
+    @Test
+    public void testSetContentAnimationRunningInGroupChild_Run() {
+        // Creates parent views on mGroupRow.
+        NotificationContentView publicParentLayout = mock(NotificationContentView.class);
+        mGroupRow.setPublicLayout(publicParentLayout);
+        NotificationContentView privateParentLayout = mock(NotificationContentView.class);
+        mGroupRow.setPrivateLayout(privateParentLayout);
+
+        // Create child views on mNotifRow.
+        NotificationContentView publicChildLayout = mock(NotificationContentView.class);
+        mNotifRow.setPublicLayout(publicChildLayout);
+        NotificationContentView privateChildLayout = mock(NotificationContentView.class);
+        mNotifRow.setPrivateLayout(privateChildLayout);
+        when(mNotifRow.isGroupExpanded()).thenReturn(true);
+        setMockChildrenContainer(mGroupRow, mNotifRow);
+
+        mGroupRow.setAnimationRunning(true);
+        verify(publicParentLayout, times(1)).setContentAnimationRunning(true);
+        verify(privateParentLayout, times(1)).setContentAnimationRunning(true);
+        // The child layouts should be started too.
+        verify(publicChildLayout, times(1)).setContentAnimationRunning(true);
+        verify(privateChildLayout, times(1)).setContentAnimationRunning(true);
+    }
+
+
+    @Test
+    public void testSetIconAnimationRunningGroup_Run() {
+        // Create views for a group row.
+        NotificationContentView publicParentLayout = mock(NotificationContentView.class);
+        mGroupRow.setPublicLayout(publicParentLayout);
+        NotificationContentView privateParentLayout = mock(NotificationContentView.class);
+        mGroupRow.setPrivateLayout(privateParentLayout);
+        when(mGroupRow.isGroupExpanded()).thenReturn(true);
+
+        // Sets up mNotifRow as a child ExpandableNotificationRow.
+        NotificationContentView publicChildLayout = mock(NotificationContentView.class);
+        mNotifRow.setPublicLayout(publicChildLayout);
+        NotificationContentView privateChildLayout = mock(NotificationContentView.class);
+        mNotifRow.setPrivateLayout(privateChildLayout);
+        when(mNotifRow.isGroupExpanded()).thenReturn(true);
+
+        NotificationChildrenContainer mockContainer =
+                setMockChildrenContainer(mGroupRow, mNotifRow);
+
+        // Mock the children view wrappers, and give them each an icon.
+        NotificationViewWrapper mockViewWrapper = mock(NotificationViewWrapper.class);
+        when(mockContainer.getNotificationViewWrapper()).thenReturn(mockViewWrapper);
+        CachingIconView mockIcon = mock(CachingIconView.class);
+        when(mockViewWrapper.getIcon()).thenReturn(mockIcon);
+
+        NotificationViewWrapper mockLowPriorityViewWrapper = mock(NotificationViewWrapper.class);
+        when(mockContainer.getLowPriorityViewWrapper()).thenReturn(mockLowPriorityViewWrapper);
+        CachingIconView mockLowPriorityIcon = mock(CachingIconView.class);
+        when(mockLowPriorityViewWrapper.getIcon()).thenReturn(mockLowPriorityIcon);
+
+        // Give the icon image views drawables, so we can make sure they animate.
+        // We use both AnimationDrawables and AnimatedVectorDrawables to ensure both work.
+        AnimationDrawable drawable = mock(AnimationDrawable.class);
+        AnimatedVectorDrawable vectorDrawable = mock(AnimatedVectorDrawable.class);
+        setDrawableIconsInImageView(mockIcon, drawable, vectorDrawable);
+
+        AnimationDrawable lowPriDrawable = mock(AnimationDrawable.class);
+        AnimatedVectorDrawable lowPriVectorDrawable = mock(AnimatedVectorDrawable.class);
+        setDrawableIconsInImageView(mockLowPriorityIcon, lowPriDrawable, lowPriVectorDrawable);
+
+        mGroupRow.setAnimationRunning(true);
+        verify(drawable, times(1)).start();
+        verify(vectorDrawable, times(1)).start();
+        verify(lowPriDrawable, times(1)).start();
+        verify(lowPriVectorDrawable, times(1)).start();
+    }
+
+    private void setDrawableIconsInImageView(CachingIconView icon, Drawable iconDrawable,
+            Drawable rightIconDrawable) {
+        ImageView iconView = mock(ImageView.class);
+        when(icon.findViewById(com.android.internal.R.id.icon)).thenReturn(iconView);
+        when(iconView.getDrawable()).thenReturn(iconDrawable);
+
+        ImageView rightIconView = mock(ImageView.class);
+        when(icon.findViewById(com.android.internal.R.id.right_icon)).thenReturn(rightIconView);
+        when(rightIconView.getDrawable()).thenReturn(rightIconDrawable);
+    }
+
+    private NotificationChildrenContainer setMockChildrenContainer(
+            ExpandableNotificationRow parentRow, ExpandableNotificationRow childRow) {
+        List<ExpandableNotificationRow> rowList = Arrays.asList(childRow);
+        NotificationChildrenContainer mockContainer = mock(NotificationChildrenContainer.class);
+        when(mockContainer.getNotificationChildCount()).thenReturn(1);
+        when(mockContainer.getAttachedChildren()).thenReturn(rowList);
+        parentRow.setChildrenContainer(mockContainer);
+        return mockContainer;
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentViewTest.kt
index 562b4df..7b2051d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentViewTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentViewTest.kt
@@ -34,9 +34,12 @@
 import com.android.systemui.statusbar.notification.FeedbackIcon
 import com.android.systemui.statusbar.notification.collection.NotificationEntry
 import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier
+import com.android.systemui.statusbar.notification.row.wrapper.NotificationViewWrapper
 import com.android.systemui.util.mockito.mock
 import com.android.systemui.util.mockito.whenever
 import junit.framework.Assert.assertEquals
+import junit.framework.Assert.assertFalse
+import junit.framework.Assert.assertTrue
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -44,6 +47,7 @@
 import org.mockito.Mockito.doReturn
 import org.mockito.Mockito.never
 import org.mockito.Mockito.spy
+import org.mockito.Mockito.times
 import org.mockito.Mockito.verify
 import org.mockito.MockitoAnnotations.initMocks
 
@@ -305,6 +309,86 @@
         assertEquals(0, getMarginBottom(actionListMarginTarget))
     }
 
+    @Test
+    fun onSetAnimationRunning() {
+        // Given: contractedWrapper, enpandedWrapper, and headsUpWrapper being set
+        val mockContracted = mock<NotificationViewWrapper>()
+        val mockExpanded = mock<NotificationViewWrapper>()
+        val mockHeadsUp = mock<NotificationViewWrapper>()
+
+        view.setContractedWrapper(mockContracted)
+        view.setExpandedWrapper(mockExpanded)
+        view.setHeadsUpWrapper(mockHeadsUp)
+
+        // When: we set content animation running.
+        assertTrue(view.setContentAnimationRunning(true))
+
+        // Then: contractedChild, expandedChild, and headsUpChild should have setAnimationsRunning
+        // called on them.
+        verify(mockContracted, times(1)).setAnimationsRunning(true)
+        verify(mockExpanded, times(1)).setAnimationsRunning(true)
+        verify(mockHeadsUp, times(1)).setAnimationsRunning(true)
+
+        // When: we set content animation running true _again_.
+        assertFalse(view.setContentAnimationRunning(true))
+
+        // Then: the children should not have setAnimationRunning called on them again.
+        // Verify counts number of calls so far on the object, so these still register as 1.
+        verify(mockContracted, times(1)).setAnimationsRunning(true)
+        verify(mockExpanded, times(1)).setAnimationsRunning(true)
+        verify(mockHeadsUp, times(1)).setAnimationsRunning(true)
+    }
+
+    @Test
+    fun onSetAnimationStopped() {
+        // Given: contractedWrapper, expandedWrapper, and headsUpWrapper being set
+        val mockContracted = mock<NotificationViewWrapper>()
+        val mockExpanded = mock<NotificationViewWrapper>()
+        val mockHeadsUp = mock<NotificationViewWrapper>()
+
+        view.setContractedWrapper(mockContracted)
+        view.setExpandedWrapper(mockExpanded)
+        view.setHeadsUpWrapper(mockHeadsUp)
+
+        // When: we set content animation running.
+        assertTrue(view.setContentAnimationRunning(true))
+
+        // Then: contractedChild, expandedChild, and headsUpChild should have setAnimationsRunning
+        // called on them.
+        verify(mockContracted).setAnimationsRunning(true)
+        verify(mockExpanded).setAnimationsRunning(true)
+        verify(mockHeadsUp).setAnimationsRunning(true)
+
+        // When: we set content animation running false, the state changes, so the function
+        // returns true.
+        assertTrue(view.setContentAnimationRunning(false))
+
+        // Then: the children have their animations stopped.
+        verify(mockContracted).setAnimationsRunning(false)
+        verify(mockExpanded).setAnimationsRunning(false)
+        verify(mockHeadsUp).setAnimationsRunning(false)
+    }
+
+    @Test
+    fun onSetAnimationInitStopped() {
+        // Given: contractedWrapper, expandedWrapper, and headsUpWrapper being set
+        val mockContracted = mock<NotificationViewWrapper>()
+        val mockExpanded = mock<NotificationViewWrapper>()
+        val mockHeadsUp = mock<NotificationViewWrapper>()
+
+        view.setContractedWrapper(mockContracted)
+        view.setExpandedWrapper(mockExpanded)
+        view.setHeadsUpWrapper(mockHeadsUp)
+
+        // When: we try to stop the animations before they've been started.
+        assertFalse(view.setContentAnimationRunning(false))
+
+        // Then: the children should not have setAnimationRunning called on them again.
+        verify(mockContracted, never()).setAnimationsRunning(false)
+        verify(mockExpanded, never()).setAnimationsRunning(false)
+        verify(mockHeadsUp, never()).setAnimationsRunning(false)
+    }
+
     private fun createMockContainingNotification(notificationEntry: NotificationEntry) =
         mock<ExpandableNotificationRow>().apply {
             whenever(this.entry).thenReturn(notificationEntry)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java
index 728e026..e4fc4d5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java
@@ -53,6 +53,7 @@
 import com.android.systemui.TestableDependency;
 import com.android.systemui.classifier.FalsingCollectorFake;
 import com.android.systemui.classifier.FalsingManagerFake;
+import com.android.systemui.flags.FeatureFlags;
 import com.android.systemui.media.controls.util.MediaFeatureFlag;
 import com.android.systemui.media.dialog.MediaOutputDialogFactory;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
@@ -130,6 +131,7 @@
     public final OnUserInteractionCallback mOnUserInteractionCallback;
     public final Runnable mFutureDismissalRunnable;
     private @InflationFlag int mDefaultInflationFlags;
+    private FeatureFlags mFeatureFlags;
 
     public NotificationTestHelper(
             Context context,
@@ -193,12 +195,17 @@
         mFutureDismissalRunnable = mock(Runnable.class);
         when(mOnUserInteractionCallback.registerFutureDismissal(any(), anyInt()))
                 .thenReturn(mFutureDismissalRunnable);
+        mFeatureFlags = mock(FeatureFlags.class);
     }
 
     public void setDefaultInflationFlags(@InflationFlag int defaultInflationFlags) {
         mDefaultInflationFlags = defaultInflationFlags;
     }
 
+    public void setFeatureFlags(FeatureFlags featureFlags) {
+        mFeatureFlags = featureFlags;
+    }
+
     public ExpandableNotificationRowLogger getMockLogger() {
         return mMockLogger;
     }
@@ -561,7 +568,8 @@
                 mock(NotificationGutsManager.class),
                 mock(MetricsLogger.class),
                 mock(SmartReplyConstants.class),
-                mock(SmartReplyController.class));
+                mock(SmartReplyController.class),
+                mFeatureFlags);
 
         row.setAboveShelfChangedListener(aboveShelf -> { });
         mBindStage.getStageParams(entry).requireContentViews(extraInflationFlags);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationBigPictureTemplateViewWrapperTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationBigPictureTemplateViewWrapperTest.java
index 509ba41..8f88501 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationBigPictureTemplateViewWrapperTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationBigPictureTemplateViewWrapperTest.java
@@ -16,11 +16,12 @@
 
 package com.android.systemui.statusbar.notification.row.wrapper;
 
+import static org.junit.Assert.assertNotNull;
 import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.when;
+import static org.mockito.Mockito.verify;
 
 import android.app.Notification;
-import android.content.Context;
+import android.graphics.drawable.AnimatedImageDrawable;
 import android.graphics.drawable.Icon;
 import android.os.Bundle;
 import android.testing.AndroidTestingRunner;
@@ -28,11 +29,11 @@
 import android.testing.TestableLooper.RunWithLooper;
 import android.view.LayoutInflater;
 import android.view.View;
-import android.widget.LinearLayout;
-import android.widget.TextView;
 
 import androidx.test.filters.SmallTest;
 
+import com.android.internal.R;
+import com.android.internal.widget.BigPictureNotificationImageView;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
 import com.android.systemui.statusbar.notification.row.NotificationTestHelper;
@@ -73,4 +74,38 @@
                 Notification.EXTRA_LARGE_ICON_BIG, new Bundle());
         wrapper.onContentUpdated(mRow);
     }
+
+    @Test
+    public void setAnimationsRunning_Run() {
+        BigPictureNotificationImageView imageView = mView.findViewById(R.id.big_picture);
+        AnimatedImageDrawable mockDrawable = mock(AnimatedImageDrawable.class);
+
+        assertNotNull(imageView);
+        imageView.setImageDrawable(mockDrawable);
+
+        NotificationViewWrapper wrapper = new NotificationBigPictureTemplateViewWrapper(mContext,
+                mView, mRow);
+        // Required to re-initialize the imageView to the imageView created above.
+        wrapper.onContentUpdated(mRow);
+
+        wrapper.setAnimationsRunning(true);
+        verify(mockDrawable).start();
+    }
+
+    @Test
+    public void setAnimationsRunning_Stop() {
+        BigPictureNotificationImageView imageView = mView.findViewById(R.id.big_picture);
+        AnimatedImageDrawable mockDrawable = mock(AnimatedImageDrawable.class);
+
+        assertNotNull(imageView);
+        imageView.setImageDrawable(mockDrawable);
+
+        NotificationViewWrapper wrapper = new NotificationBigPictureTemplateViewWrapper(mContext,
+                mView, mRow);
+        // Required to re-initialize the imageView to the imageView created above.
+        wrapper.onContentUpdated(mRow);
+
+        wrapper.setAnimationsRunning(false);
+        verify(mockDrawable).stop();
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationConversationTemplateViewWrapperTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationConversationTemplateViewWrapperTest.kt
new file mode 100644
index 0000000..3fa68bb
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationConversationTemplateViewWrapperTest.kt
@@ -0,0 +1,143 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.row.wrapper
+
+import android.graphics.drawable.AnimatedImageDrawable
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper
+import android.testing.TestableLooper.RunWithLooper
+import android.view.View
+import androidx.test.filters.SmallTest
+import com.android.internal.R
+import com.android.internal.widget.CachingIconView
+import com.android.internal.widget.ConversationLayout
+import com.android.internal.widget.MessagingGroup
+import com.android.internal.widget.MessagingImageMessage
+import com.android.internal.widget.MessagingLinearLayout
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
+import com.android.systemui.statusbar.notification.row.NotificationTestHelper
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.mock
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mockito.verify
+import org.mockito.Mockito.`when` as whenever
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+@RunWithLooper
+class NotificationConversationTemplateViewWrapperTest : SysuiTestCase() {
+
+    private lateinit var mRow: ExpandableNotificationRow
+    private lateinit var helper: NotificationTestHelper
+
+    @Before
+    fun setUp() {
+        allowTestableLooperAsMainThread()
+        helper = NotificationTestHelper(mContext, mDependency, TestableLooper.get(this))
+        mRow = helper.createRow()
+    }
+
+    @Test
+    fun setAnimationsRunning_Run() {
+        // Creates a mocked out NotificationEntry of ConversationLayout type,
+        // with a mock imageMessage.drawable embedded in its MessagingImageMessages
+        // (both top level, and in a group).
+        val mockDrawable = mock<AnimatedImageDrawable>()
+        val mockDrawable2 = mock<AnimatedImageDrawable>()
+        val mockLayoutView: View = fakeConversationLayout(mockDrawable, mockDrawable2)
+
+        val wrapper: NotificationViewWrapper =
+            NotificationConversationTemplateViewWrapper(mContext, mockLayoutView, mRow)
+        wrapper.onContentUpdated(mRow)
+        wrapper.setAnimationsRunning(true)
+
+        // Verifies that each AnimatedImageDrawable is started animating.
+        verify(mockDrawable).start()
+        verify(mockDrawable2).start()
+    }
+
+    @Test
+    fun setAnimationsRunning_Stop() {
+        // Creates a mocked out NotificationEntry of ConversationLayout type,
+        // with a mock imageMessage.drawable embedded in its MessagingImageMessages
+        // (both top level, and in a group).
+        val mockDrawable = mock<AnimatedImageDrawable>()
+        val mockDrawable2 = mock<AnimatedImageDrawable>()
+        val mockLayoutView: View = fakeConversationLayout(mockDrawable, mockDrawable2)
+
+        val wrapper: NotificationViewWrapper =
+            NotificationConversationTemplateViewWrapper(mContext, mockLayoutView, mRow)
+        wrapper.onContentUpdated(mRow)
+        wrapper.setAnimationsRunning(false)
+
+        // Verifies that each AnimatedImageDrawable is started animating.
+        verify(mockDrawable).stop()
+        verify(mockDrawable2).stop()
+    }
+
+    private fun fakeConversationLayout(
+        mockDrawableGroupMessage: AnimatedImageDrawable,
+        mockDrawableImageMessage: AnimatedImageDrawable
+    ): View {
+        val mockMessagingImageMessage: MessagingImageMessage =
+            mock<MessagingImageMessage>().apply {
+                whenever(drawable).thenReturn(mockDrawableImageMessage)
+            }
+        val mockImageMessageContainer: MessagingLinearLayout =
+            mock<MessagingLinearLayout>().apply {
+                whenever(childCount).thenReturn(1)
+                whenever(getChildAt(any())).thenReturn(mockMessagingImageMessage)
+            }
+
+        val mockMessagingImageMessageForGroup: MessagingImageMessage =
+            mock<MessagingImageMessage>().apply {
+                whenever(drawable).thenReturn(mockDrawableGroupMessage)
+            }
+        val mockMessageContainer: MessagingLinearLayout =
+            mock<MessagingLinearLayout>().apply {
+                whenever(childCount).thenReturn(1)
+                whenever(getChildAt(any())).thenReturn(mockMessagingImageMessageForGroup)
+            }
+        val mockGroup: MessagingGroup =
+            mock<MessagingGroup>().apply {
+                whenever(messageContainer).thenReturn(mockMessageContainer)
+            }
+        val mockView: View =
+            mock<ConversationLayout>().apply {
+                whenever(messagingGroups).thenReturn(ArrayList<MessagingGroup>(listOf(mockGroup)))
+                whenever(imageMessageContainer).thenReturn(mockImageMessageContainer)
+                whenever(messagingLinearLayout).thenReturn(mockMessageContainer)
+
+                // These must be mocked as they're required to be nonnull.
+                whenever(requireViewById<View>(R.id.conversation_icon_container)).thenReturn(mock())
+                whenever(requireViewById<CachingIconView>(R.id.conversation_icon))
+                    .thenReturn(mock())
+                whenever(findViewById<CachingIconView>(R.id.icon)).thenReturn(mock())
+                whenever(requireViewById<View>(R.id.conversation_icon_badge_bg)).thenReturn(mock())
+                whenever(requireViewById<View>(R.id.expand_button)).thenReturn(mock())
+                whenever(requireViewById<View>(R.id.expand_button_container)).thenReturn(mock())
+                whenever(requireViewById<View>(R.id.conversation_icon_badge_ring))
+                    .thenReturn(mock())
+                whenever(requireViewById<View>(R.id.app_name_text)).thenReturn(mock())
+                whenever(requireViewById<View>(R.id.conversation_text)).thenReturn(mock())
+            }
+        return mockView
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationMessagingTemplateViewWrapperTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationMessagingTemplateViewWrapperTest.kt
new file mode 100644
index 0000000..c0444b5
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationMessagingTemplateViewWrapperTest.kt
@@ -0,0 +1,103 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.row.wrapper
+
+import android.graphics.drawable.AnimatedImageDrawable
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper
+import android.testing.TestableLooper.RunWithLooper
+import android.view.View
+import androidx.test.filters.SmallTest
+import com.android.internal.widget.MessagingGroup
+import com.android.internal.widget.MessagingImageMessage
+import com.android.internal.widget.MessagingLayout
+import com.android.internal.widget.MessagingLinearLayout
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
+import com.android.systemui.statusbar.notification.row.NotificationTestHelper
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.mock
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mockito.verify
+import org.mockito.Mockito.`when` as whenever
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+@RunWithLooper
+class NotificationMessagingTemplateViewWrapperTest : SysuiTestCase() {
+
+    private lateinit var mRow: ExpandableNotificationRow
+    private lateinit var helper: NotificationTestHelper
+
+    @Before
+    fun setUp() {
+        allowTestableLooperAsMainThread()
+        helper = NotificationTestHelper(mContext, mDependency, TestableLooper.get(this))
+        mRow = helper.createRow()
+    }
+
+    @Test
+    fun setAnimationsRunning_Run() {
+        // Creates a mocked out NotificationEntry of MessagingLayout/MessagingStyle type,
+        // with a mock imageMessage.drawable embedded in its MessagingImageMessage.
+        val mockDrawable = mock<AnimatedImageDrawable>()
+        val mockLayoutView: View = fakeMessagingLayout(mockDrawable)
+
+        val wrapper: NotificationViewWrapper =
+            NotificationMessagingTemplateViewWrapper(mContext, mockLayoutView, mRow)
+        wrapper.setAnimationsRunning(true)
+
+        // Verifies that each AnimatedImageDrawable is started animating.
+        verify(mockDrawable).start()
+    }
+
+    @Test
+    fun setAnimationsRunning_Stop() {
+        // Creates a mocked out NotificationEntry of MessagingLayout/MessagingStyle type,
+        // with a mock imageMessage.drawable embedded in its MessagingImageMessage.
+        val mockDrawable = mock<AnimatedImageDrawable>()
+        val mockLayoutView: View = fakeMessagingLayout(mockDrawable)
+
+        val wrapper: NotificationViewWrapper =
+            NotificationMessagingTemplateViewWrapper(mContext, mockLayoutView, mRow)
+        wrapper.setAnimationsRunning(false)
+
+        // Verifies that each AnimatedImageDrawable is started animating.
+        verify(mockDrawable).stop()
+    }
+
+    private fun fakeMessagingLayout(mockDrawable: AnimatedImageDrawable): View {
+        val mockMessagingImageMessage: MessagingImageMessage =
+            mock<MessagingImageMessage>().apply { whenever(drawable).thenReturn(mockDrawable) }
+        val mockMessageContainer: MessagingLinearLayout =
+            mock<MessagingLinearLayout>().apply {
+                whenever(childCount).thenReturn(1)
+                whenever(getChildAt(any())).thenReturn(mockMessagingImageMessage)
+            }
+        val mockGroup: MessagingGroup =
+            mock<MessagingGroup>().apply {
+                whenever(messageContainer).thenReturn(mockMessageContainer)
+            }
+        val mockView: View =
+            mock<MessagingLayout>().apply {
+                whenever(messagingGroups).thenReturn(ArrayList<MessagingGroup>(listOf(mockGroup)))
+            }
+        return mockView
+    }
+}
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index 1ba7e68..8619615 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -4715,6 +4715,7 @@
                 mTaskChangeNotificationController.notifyTaskFocusChanged(prevTask.mTaskId, false);
             }
             mTaskChangeNotificationController.notifyTaskFocusChanged(task.mTaskId, true);
+            mTaskSupervisor.mRecentTasks.add(task);
         }
 
         applyUpdateLockStateLocked(r);
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityTaskSupervisorTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityTaskSupervisorTests.java
index eed32d7..bb20244 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityTaskSupervisorTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityTaskSupervisorTests.java
@@ -249,6 +249,19 @@
     }
 
     /**
+     * Ensures it updates recent tasks order when the last resumed activity changed.
+     */
+    @Test
+    public void testUpdateRecentTasksForTopResumed() {
+        spyOn(mSupervisor.mRecentTasks);
+        final ActivityRecord activity = new ActivityBuilder(mAtm).setCreateTask(true).build();
+        final Task task = activity.getTask();
+
+        mAtm.setLastResumedActivityUncheckLocked(activity, "test");
+        verify(mSupervisor.mRecentTasks).add(eq(task));
+    }
+
+    /**
      * Ensures that a trusted display can launch arbitrary activity and an untrusted display can't.
      */
     @Test