Merge "Skip syncing local window for rotation with shell transition" into tm-qpr-dev
diff --git a/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java b/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java
index e8bcfd2..c94cc8f 100644
--- a/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java
+++ b/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java
@@ -2895,7 +2895,11 @@
                 } else {
                     needsPermission = false;
                     lowerQuota = allowWhileIdle;
-                    idleOptions = allowWhileIdle ? mOptsWithFgs.toBundle() : null;
+                    idleOptions = (allowWhileIdle || (alarmClock != null))
+                            // This avoids exceptions on existing alarms when the app upgrades to
+                            // target S. Note that FGS from pre-S apps isn't restricted anyway.
+                            ? mOptsWithFgs.toBundle()
+                            : null;
                     if (exact) {
                         exactAllowReason = EXACT_ALLOW_REASON_COMPAT;
                     }
diff --git a/core/java/android/app/NotificationChannelGroup.java b/core/java/android/app/NotificationChannelGroup.java
index f97415c..1769993e 100644
--- a/core/java/android/app/NotificationChannelGroup.java
+++ b/core/java/android/app/NotificationChannelGroup.java
@@ -20,6 +20,7 @@
 import android.annotation.TestApi;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.content.Intent;
+import android.content.pm.ParceledListSlice;
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.text.TextUtils;
@@ -66,7 +67,7 @@
     private CharSequence mName;
     private String mDescription;
     private boolean mBlocked;
-    private List<NotificationChannel> mChannels = new ArrayList<>();
+    private ParceledListSlice<NotificationChannel> mChannels;
     // Bitwise representation of fields that have been changed by the user
     private int mUserLockedFields;
 
@@ -100,7 +101,8 @@
         } else {
             mDescription = null;
         }
-        in.readParcelableList(mChannels, NotificationChannel.class.getClassLoader(), android.app.NotificationChannel.class);
+        mChannels = in.readParcelable(
+                NotificationChannelGroup.class.getClassLoader(), ParceledListSlice.class);
         mBlocked = in.readBoolean();
         mUserLockedFields = in.readInt();
     }
@@ -127,7 +129,7 @@
         } else {
             dest.writeByte((byte) 0);
         }
-        dest.writeParcelableList(mChannels, flags);
+        dest.writeParcelable(mChannels, flags);
         dest.writeBoolean(mBlocked);
         dest.writeInt(mUserLockedFields);
     }
@@ -157,7 +159,7 @@
      * Returns the list of channels that belong to this group
      */
     public List<NotificationChannel> getChannels() {
-        return mChannels;
+        return mChannels == null ? new ArrayList<>() : mChannels.getList();
     }
 
     /**
@@ -191,15 +193,8 @@
     /**
      * @hide
      */
-    public void addChannel(NotificationChannel channel) {
-        mChannels.add(channel);
-    }
-
-    /**
-     * @hide
-     */
     public void setChannels(List<NotificationChannel> channels) {
-        mChannels = channels;
+        mChannels = new ParceledListSlice<>(channels);
     }
 
     /**
@@ -334,7 +329,7 @@
         proto.write(NotificationChannelGroupProto.NAME, mName.toString());
         proto.write(NotificationChannelGroupProto.DESCRIPTION, mDescription);
         proto.write(NotificationChannelGroupProto.IS_BLOCKED, mBlocked);
-        for (NotificationChannel channel : mChannels) {
+        for (NotificationChannel channel : mChannels.getList()) {
             channel.dumpDebug(proto, NotificationChannelGroupProto.CHANNELS);
         }
         proto.end(token);
diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java
index 44dc28d..c15b3e0 100644
--- a/core/java/android/content/pm/PackageParser.java
+++ b/core/java/android/content/pm/PackageParser.java
@@ -713,6 +713,15 @@
         if (!checkUseInstalledOrHidden(flags, state, p.applicationInfo) || !p.isMatch(flags)) {
             return null;
         }
+
+        final ApplicationInfo applicationInfo;
+        if ((flags & (PackageManager.GET_ACTIVITIES | PackageManager.GET_RECEIVERS
+                | PackageManager.GET_SERVICES | PackageManager.GET_PROVIDERS)) != 0) {
+            applicationInfo = generateApplicationInfo(p, flags, state, userId);
+        } else {
+            applicationInfo = null;
+        }
+
         PackageInfo pi = new PackageInfo();
         pi.packageName = p.packageName;
         pi.splitNames = p.splitNames;
@@ -773,7 +782,7 @@
                         if (PackageManager.APP_DETAILS_ACTIVITY_CLASS_NAME.equals(a.className)) {
                             continue;
                         }
-                        res[num++] = generateActivityInfo(a, flags, state, userId);
+                        res[num++] = generateActivityInfo(a, flags, state, userId, applicationInfo);
                     }
                 }
                 pi.activities = ArrayUtils.trimToSize(res, num);
@@ -787,7 +796,7 @@
                 for (int i = 0; i < N; i++) {
                     final Activity a = p.receivers.get(i);
                     if (isMatch(state, a.info, flags)) {
-                        res[num++] = generateActivityInfo(a, flags, state, userId);
+                        res[num++] = generateActivityInfo(a, flags, state, userId, applicationInfo);
                     }
                 }
                 pi.receivers = ArrayUtils.trimToSize(res, num);
@@ -801,7 +810,7 @@
                 for (int i = 0; i < N; i++) {
                     final Service s = p.services.get(i);
                     if (isMatch(state, s.info, flags)) {
-                        res[num++] = generateServiceInfo(s, flags, state, userId);
+                        res[num++] = generateServiceInfo(s, flags, state, userId, applicationInfo);
                     }
                 }
                 pi.services = ArrayUtils.trimToSize(res, num);
@@ -815,7 +824,8 @@
                 for (int i = 0; i < N; i++) {
                     final Provider pr = p.providers.get(i);
                     if (isMatch(state, pr.info, flags)) {
-                        res[num++] = generateProviderInfo(pr, flags, state, userId);
+                        res[num++] = generateProviderInfo(pr, flags, state, userId,
+                                applicationInfo);
                     }
                 }
                 pi.providers = ArrayUtils.trimToSize(res, num);
@@ -8216,6 +8226,11 @@
     @UnsupportedAppUsage
     public static final ActivityInfo generateActivityInfo(Activity a, int flags,
             FrameworkPackageUserState state, int userId) {
+        return generateActivityInfo(a, flags, state, userId, null);
+    }
+
+    private static ActivityInfo generateActivityInfo(Activity a, int flags,
+            FrameworkPackageUserState state, int userId, ApplicationInfo applicationInfo) {
         if (a == null) return null;
         if (!checkUseInstalledOrHidden(flags, state, a.owner.applicationInfo)) {
             return null;
@@ -8227,7 +8242,12 @@
         // Make shallow copies so we can store the metadata safely
         ActivityInfo ai = new ActivityInfo(a.info);
         ai.metaData = a.metaData;
-        ai.applicationInfo = generateApplicationInfo(a.owner, flags, state, userId);
+
+        if (applicationInfo == null) {
+            applicationInfo = generateApplicationInfo(a.owner, flags, state, userId);
+        }
+        ai.applicationInfo = applicationInfo;
+
         return ai;
     }
 
@@ -8308,6 +8328,11 @@
     @UnsupportedAppUsage
     public static final ServiceInfo generateServiceInfo(Service s, int flags,
             FrameworkPackageUserState state, int userId) {
+        return generateServiceInfo(s, flags, state, userId, null);
+    }
+
+    private static ServiceInfo generateServiceInfo(Service s, int flags,
+            FrameworkPackageUserState state, int userId, ApplicationInfo applicationInfo) {
         if (s == null) return null;
         if (!checkUseInstalledOrHidden(flags, state, s.owner.applicationInfo)) {
             return null;
@@ -8319,7 +8344,12 @@
         // Make shallow copies so we can store the metadata safely
         ServiceInfo si = new ServiceInfo(s.info);
         si.metaData = s.metaData;
-        si.applicationInfo = generateApplicationInfo(s.owner, flags, state, userId);
+
+        if (applicationInfo == null) {
+            applicationInfo = generateApplicationInfo(s.owner, flags, state, userId);
+        }
+        si.applicationInfo = applicationInfo;
+
         return si;
     }
 
@@ -8406,13 +8436,18 @@
     @UnsupportedAppUsage
     public static final ProviderInfo generateProviderInfo(Provider p, int flags,
             FrameworkPackageUserState state, int userId) {
+        return generateProviderInfo(p, flags, state, userId, null);
+    }
+
+    private static ProviderInfo generateProviderInfo(Provider p, int flags,
+            FrameworkPackageUserState state, int userId, ApplicationInfo applicationInfo) {
         if (p == null) return null;
         if (!checkUseInstalledOrHidden(flags, state, p.owner.applicationInfo)) {
             return null;
         }
         if (!copyNeeded(flags, p.owner, state, p.metaData, userId)
                 && ((flags & PackageManager.GET_URI_PERMISSION_PATTERNS) != 0
-                        || p.info.uriPermissionPatterns == null)) {
+                || p.info.uriPermissionPatterns == null)) {
             updateApplicationInfo(p.info.applicationInfo, flags, state);
             return p.info;
         }
@@ -8422,7 +8457,12 @@
         if ((flags & PackageManager.GET_URI_PERMISSION_PATTERNS) == 0) {
             pi.uriPermissionPatterns = null;
         }
-        pi.applicationInfo = generateApplicationInfo(p.owner, flags, state, userId);
+
+        if (applicationInfo == null) {
+            applicationInfo = generateApplicationInfo(p.owner, flags, state, userId);
+        }
+        pi.applicationInfo = applicationInfo;
+
         return pi;
     }
 
diff --git a/core/java/android/hardware/camera2/marshal/impl/MarshalQueryableArray.java b/core/java/android/hardware/camera2/marshal/impl/MarshalQueryableArray.java
index cbd8066..598170d 100644
--- a/core/java/android/hardware/camera2/marshal/impl/MarshalQueryableArray.java
+++ b/core/java/android/hardware/camera2/marshal/impl/MarshalQueryableArray.java
@@ -26,6 +26,10 @@
 
 import java.lang.reflect.Array;
 import java.nio.ByteBuffer;
+import java.nio.DoubleBuffer;
+import java.nio.FloatBuffer;
+import java.nio.IntBuffer;
+import java.nio.LongBuffer;
 import java.util.ArrayList;
 
 /**
@@ -51,28 +55,36 @@
                 return new PrimitiveArrayFiller() {
                       @Override
                       public void fillArray(Object arr, int size, ByteBuffer buffer) {
-                          buffer.asIntBuffer().get(int[].class.cast(arr), 0, size);
+                          IntBuffer ib = buffer.asIntBuffer().get(int[].class.cast(arr), 0, size);
+                          // Update buffer position since the IntBuffer has independent position.
+                          buffer.position(buffer.position() + ib.position() * Integer.BYTES);
                       }
                 };
             } else if (componentType == float.class) {
                 return new PrimitiveArrayFiller() {
                       @Override
                       public void fillArray(Object arr, int size, ByteBuffer buffer) {
-                          buffer.asFloatBuffer().get(float[].class.cast(arr), 0, size);
+                          FloatBuffer fb =
+                                  buffer.asFloatBuffer().get(float[].class.cast(arr), 0, size);
+                          buffer.position(buffer.position() + fb.position() * Float.BYTES);
                       }
                 };
             } else if (componentType == long.class) {
                 return new PrimitiveArrayFiller() {
                       @Override
                       public void fillArray(Object arr, int size, ByteBuffer buffer) {
-                          buffer.asLongBuffer().get(long[].class.cast(arr), 0, size);
+                          LongBuffer lb =
+                                  buffer.asLongBuffer().get(long[].class.cast(arr), 0, size);
+                          buffer.position(buffer.position() + lb.position() * Long.BYTES);
                       }
                 };
             } else if (componentType == double.class) {
                 return new PrimitiveArrayFiller() {
                       @Override
                       public void fillArray(Object arr, int size, ByteBuffer buffer) {
-                          buffer.asDoubleBuffer().get(double[].class.cast(arr), 0, size);
+                          DoubleBuffer db =
+                                  buffer.asDoubleBuffer().get(double[].class.cast(arr), 0, size);
+                          buffer.position(buffer.position() + db.position() * Double.BYTES);
                       }
                 };
             } else if (componentType == byte.class) {
diff --git a/core/java/android/util/FeatureFlagUtils.java b/core/java/android/util/FeatureFlagUtils.java
index 6049199..195cf82 100644
--- a/core/java/android/util/FeatureFlagUtils.java
+++ b/core/java/android/util/FeatureFlagUtils.java
@@ -86,7 +86,12 @@
     /** Flag to enable/disable guest mode UX changes as mentioned in b/214031645
      *  @hide
      */
-    public static  final String SETTINGS_GUEST_MODE_UX_CHANGES = "settings_guest_mode_ux_changes";
+    public static final String SETTINGS_GUEST_MODE_UX_CHANGES = "settings_guest_mode_ux_changes";
+
+    /** Support Clear Calling feature.
+     *  @hide
+     */
+    public static final String SETTINGS_ENABLE_CLEAR_CALLING = "settings_enable_clear_calling";
 
     private static final Map<String, String> DEFAULT_FLAGS;
 
@@ -116,6 +121,7 @@
         DEFAULT_FLAGS.put(SETTINGS_APP_ALLOW_DARK_THEME_ACTIVATION_AT_BEDTIME, "true");
         DEFAULT_FLAGS.put(SETTINGS_HIDE_SECOND_LAYER_PAGE_NAVIGATE_UP_BUTTON_IN_TWO_PANE, "true");
         DEFAULT_FLAGS.put(SETTINGS_GUEST_MODE_UX_CHANGES, "true");
+        DEFAULT_FLAGS.put(SETTINGS_ENABLE_CLEAR_CALLING, "false");
     }
 
     private static final Set<String> PERSISTENT_FLAGS;
diff --git a/core/java/android/view/InsetsState.java b/core/java/android/view/InsetsState.java
index 6a0ec33..c198098 100644
--- a/core/java/android/view/InsetsState.java
+++ b/core/java/android/view/InsetsState.java
@@ -90,8 +90,10 @@
             ITYPE_IME,
             ITYPE_CLIMATE_BAR,
             ITYPE_EXTRA_NAVIGATION_BAR,
-            ITYPE_LOCAL_NAVIGATION_BAR_1,
-            ITYPE_LOCAL_NAVIGATION_BAR_2
+            ITYPE_LEFT_GENERIC_OVERLAY,
+            ITYPE_TOP_GENERIC_OVERLAY,
+            ITYPE_RIGHT_GENERIC_OVERLAY,
+            ITYPE_BOTTOM_GENERIC_OVERLAY
     })
     public @interface InternalInsetsType {}
 
@@ -135,10 +137,12 @@
     public static final int ITYPE_EXTRA_NAVIGATION_BAR = 21;
 
     /** Additional types for local insets. **/
-    public static final int ITYPE_LOCAL_NAVIGATION_BAR_1 = 22;
-    public static final int ITYPE_LOCAL_NAVIGATION_BAR_2 = 23;
+    public static final int ITYPE_LEFT_GENERIC_OVERLAY = 22;
+    public static final int ITYPE_TOP_GENERIC_OVERLAY = 23;
+    public static final int ITYPE_RIGHT_GENERIC_OVERLAY = 24;
+    public static final int ITYPE_BOTTOM_GENERIC_OVERLAY = 25;
 
-    static final int LAST_TYPE = ITYPE_LOCAL_NAVIGATION_BAR_2;
+    static final int LAST_TYPE = ITYPE_BOTTOM_GENERIC_OVERLAY;
     public static final int SIZE = LAST_TYPE + 1;
 
     // Derived types
@@ -698,8 +702,12 @@
         if ((types & Type.NAVIGATION_BARS) != 0) {
             result.add(ITYPE_NAVIGATION_BAR);
             result.add(ITYPE_EXTRA_NAVIGATION_BAR);
-            result.add(ITYPE_LOCAL_NAVIGATION_BAR_1);
-            result.add(ITYPE_LOCAL_NAVIGATION_BAR_2);
+        }
+        if ((types & Type.GENERIC_OVERLAYS) != 0) {
+            result.add(ITYPE_LEFT_GENERIC_OVERLAY);
+            result.add(ITYPE_TOP_GENERIC_OVERLAY);
+            result.add(ITYPE_RIGHT_GENERIC_OVERLAY);
+            result.add(ITYPE_BOTTOM_GENERIC_OVERLAY);
         }
         if ((types & Type.CAPTION_BAR) != 0) {
             result.add(ITYPE_CAPTION_BAR);
@@ -740,9 +748,12 @@
                 return Type.STATUS_BARS;
             case ITYPE_NAVIGATION_BAR:
             case ITYPE_EXTRA_NAVIGATION_BAR:
-            case ITYPE_LOCAL_NAVIGATION_BAR_1:
-            case ITYPE_LOCAL_NAVIGATION_BAR_2:
                 return Type.NAVIGATION_BARS;
+            case ITYPE_LEFT_GENERIC_OVERLAY:
+            case ITYPE_TOP_GENERIC_OVERLAY:
+            case ITYPE_RIGHT_GENERIC_OVERLAY:
+            case ITYPE_BOTTOM_GENERIC_OVERLAY:
+                return Type.GENERIC_OVERLAYS;
             case ITYPE_CAPTION_BAR:
                 return Type.CAPTION_BAR;
             case ITYPE_IME:
@@ -861,10 +872,14 @@
                 return "ITYPE_CLIMATE_BAR";
             case ITYPE_EXTRA_NAVIGATION_BAR:
                 return "ITYPE_EXTRA_NAVIGATION_BAR";
-            case ITYPE_LOCAL_NAVIGATION_BAR_1:
-                return "ITYPE_LOCAL_NAVIGATION_BAR_1";
-            case ITYPE_LOCAL_NAVIGATION_BAR_2:
-                return "ITYPE_LOCAL_NAVIGATION_BAR_2";
+            case ITYPE_LEFT_GENERIC_OVERLAY:
+                return "ITYPE_LEFT_GENERIC_OVERLAY";
+            case ITYPE_TOP_GENERIC_OVERLAY:
+                return "ITYPE_TOP_GENERIC_OVERLAY";
+            case ITYPE_RIGHT_GENERIC_OVERLAY:
+                return "ITYPE_RIGHT_GENERIC_OVERLAY";
+            case ITYPE_BOTTOM_GENERIC_OVERLAY:
+                return "ITYPE_BOTTOM_GENERIC_OVERLAY";
             default:
                 return "ITYPE_UNKNOWN_" + type;
         }
diff --git a/core/java/android/view/WindowInsets.java b/core/java/android/view/WindowInsets.java
index c846175..c1dddbe 100644
--- a/core/java/android/view/WindowInsets.java
+++ b/core/java/android/view/WindowInsets.java
@@ -1423,9 +1423,11 @@
 
         static final int DISPLAY_CUTOUT = 1 << 7;
 
-        static final int LAST = 1 << 8;
-        static final int SIZE = 9;
-        static final int WINDOW_DECOR = LAST;
+        static final int WINDOW_DECOR = 1 << 8;
+
+        static final int GENERIC_OVERLAYS = 1 << 9;
+        static final int LAST = GENERIC_OVERLAYS;
+        static final int SIZE = 10;
 
         static int indexOf(@InsetsType int type) {
             switch (type) {
@@ -1447,6 +1449,8 @@
                     return 7;
                 case WINDOW_DECOR:
                     return 8;
+                case GENERIC_OVERLAYS:
+                    return 9;
                 default:
                     throw new IllegalArgumentException("type needs to be >= FIRST and <= LAST,"
                             + " type=" + type);
@@ -1482,6 +1486,9 @@
             if ((types & WINDOW_DECOR) != 0) {
                 result.append("windowDecor |");
             }
+            if ((types & GENERIC_OVERLAYS) != 0) {
+                result.append("genericOverlays |");
+            }
             if (result.length() > 0) {
                 result.delete(result.length() - 2, result.length());
             }
@@ -1494,7 +1501,8 @@
         /** @hide */
         @Retention(RetentionPolicy.SOURCE)
         @IntDef(flag = true, value = {STATUS_BARS, NAVIGATION_BARS, CAPTION_BAR, IME, WINDOW_DECOR,
-                SYSTEM_GESTURES, MANDATORY_SYSTEM_GESTURES, TAPPABLE_ELEMENT, DISPLAY_CUTOUT})
+                SYSTEM_GESTURES, MANDATORY_SYSTEM_GESTURES, TAPPABLE_ELEMENT, DISPLAY_CUTOUT,
+                GENERIC_OVERLAYS})
         public @interface InsetsType {
         }
 
@@ -1586,7 +1594,7 @@
          *         {@link #navigationBars()}, but not {@link #ime()}.
          */
         public static @InsetsType int systemBars() {
-            return STATUS_BARS | NAVIGATION_BARS | CAPTION_BAR;
+            return STATUS_BARS | NAVIGATION_BARS | CAPTION_BAR | GENERIC_OVERLAYS;
         }
 
         /**
diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java
index dd990e7..63d42c0 100644
--- a/core/java/android/view/WindowManager.java
+++ b/core/java/android/view/WindowManager.java
@@ -2367,6 +2367,14 @@
         public static final int SYSTEM_FLAG_SHOW_FOR_ALL_USERS = 0x00000010;
 
         /**
+         * Flag to allow this window to have unrestricted gesture exclusion.
+         *
+         * @see View#setSystemGestureExclusionRects(List)
+         * @hide
+         */
+        public static final int PRIVATE_FLAG_UNRESTRICTED_GESTURE_EXCLUSION = 0x00000020;
+
+        /**
          * Never animate position changes of the window.
          *
          * {@hide}
@@ -2586,6 +2594,7 @@
                 PRIVATE_FLAG_FORCE_HARDWARE_ACCELERATED,
                 PRIVATE_FLAG_WANTS_OFFSET_NOTIFICATIONS,
                 SYSTEM_FLAG_SHOW_FOR_ALL_USERS,
+                PRIVATE_FLAG_UNRESTRICTED_GESTURE_EXCLUSION,
                 PRIVATE_FLAG_NO_MOVE_ANIMATION,
                 PRIVATE_FLAG_COMPATIBLE_WINDOW,
                 PRIVATE_FLAG_SYSTEM_ERROR,
@@ -2633,6 +2642,10 @@
                         equals = SYSTEM_FLAG_SHOW_FOR_ALL_USERS,
                         name = "SHOW_FOR_ALL_USERS"),
                 @ViewDebug.FlagToString(
+                        mask = PRIVATE_FLAG_UNRESTRICTED_GESTURE_EXCLUSION,
+                        equals = PRIVATE_FLAG_UNRESTRICTED_GESTURE_EXCLUSION,
+                        name = "UNRESTRICTED_GESTURE_EXCLUSION"),
+                @ViewDebug.FlagToString(
                         mask = PRIVATE_FLAG_NO_MOVE_ANIMATION,
                         equals = PRIVATE_FLAG_NO_MOVE_ANIMATION,
                         name = "NO_MOVE_ANIMATION"),
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 7439b2f..5e99d6c4 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -6498,6 +6498,13 @@
     <permission android:name="android.permission.SET_UNRESTRICTED_KEEP_CLEAR_AREAS"
                 android:protectionLevel="signature|privileged" />
 
+    <!-- Allows an app to set gesture exclusion without restrictions on the vertical extent of the
+         exclusions (see {@link android.view.View#setSystemGestureExclusionRects}).
+         @hide
+    -->
+    <permission android:name="android.permission.SET_UNRESTRICTED_GESTURE_EXCLUSION"
+                android:protectionLevel="signature|privileged|recents" />
+
     <!-- @SystemApi Allows TV input apps and TV apps to use TIS extension interfaces for
          domain-specific features.
          <p>Protection level: signature|privileged|vendorPrivileged
diff --git a/core/res/res/layout-car/car_alert_dialog.xml b/core/res/res/layout-car/car_alert_dialog.xml
index 2e7b62c..3c7a5c5 100644
--- a/core/res/res/layout-car/car_alert_dialog.xml
+++ b/core/res/res/layout-car/car_alert_dialog.xml
@@ -54,7 +54,7 @@
                     android:layout_height="wrap_content"
                     android:layout_marginStart="@dimen/text_view_start_margin"
                     android:layout_marginEnd="@dimen/text_view_end_margin"
-                    style="@style/CarBody4"/>
+                    style="@style/CarDialogMessageText"/>
 
                 <!-- we don't need this spacer, but the id needs to be here for compatibility -->
                 <Space
diff --git a/core/res/res/layout-car/car_alert_dialog_button_bar.xml b/core/res/res/layout-car/car_alert_dialog_button_bar.xml
index 4f815b8..43fd1eb 100644
--- a/core/res/res/layout-car/car_alert_dialog_button_bar.xml
+++ b/core/res/res/layout-car/car_alert_dialog_button_bar.xml
@@ -16,13 +16,13 @@
   -->
 
 <ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
-            android:id="@+id/buttonPanel"
-            android:layout_width="match_parent"
-            android:layout_height="wrap_content"
-            android:scrollbarAlwaysDrawVerticalTrack="true"
-            android:scrollIndicators="top|bottom"
-            android:fillViewport="true"
-            style="?attr/buttonBarStyle">
+    android:id="@+id/buttonPanel"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:scrollbarAlwaysDrawVerticalTrack="true"
+    android:scrollIndicators="top|bottom"
+    android:fillViewport="true"
+    style="?attr/buttonBarStyle">
     <com.android.internal.widget.ButtonBarLayout
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
@@ -35,7 +35,7 @@
 
         <Button
             android:id="@+id/button3"
-            style="@style/CarAction1"
+            style="@style/CarDialogButtonText"
             android:minWidth="@dimen/car_touch_target_size"
             android:paddingStart="@dimen/car_padding_2"
             android:paddingEnd="@dimen/car_padding_2"
@@ -46,7 +46,7 @@
 
         <Button
             android:id="@+id/button2"
-            style="@style/CarAction1"
+            style="@style/CarDialogButtonText"
             android:minWidth="@dimen/car_touch_target_size"
             android:paddingStart="@dimen/car_padding_2"
             android:paddingEnd="@dimen/car_padding_2"
@@ -57,7 +57,7 @@
 
         <Button
             android:id="@+id/button1"
-            style="@style/CarAction1"
+            style="@style/CarDialogButtonText"
             android:minWidth="@dimen/car_touch_target_size"
             android:paddingStart="@dimen/car_padding_2"
             android:paddingEnd="@dimen/car_padding_2"
diff --git a/core/res/res/values/styles_car.xml b/core/res/res/values/styles_car.xml
index ca3ba93..0655fde 100644
--- a/core/res/res/values/styles_car.xml
+++ b/core/res/res/values/styles_car.xml
@@ -14,96 +14,15 @@
      limitations under the License.
 -->
 <resources>
-    <!-- Car text -->
-    <style name="CarBody1">
-        <item name="textStyle">normal</item>
-        <item name="textSize">@dimen/car_body1_size</item>
-        <item name="textColor">@color/car_body1</item>
-    </style>
-
-    <style name="CarBody1.Light">
-        <item name="textColor">@color/car_body1_light</item>
-    </style>
-
-    <style name="CarBody1.Dark">
-        <item name="textColor">@color/car_body2_dark</item>
-    </style>
-
-    <style name="CarBody2">
-        <item name="textStyle">normal</item>
-        <item name="textSize">@dimen/car_body2_size</item>
-        <item name="textColor">@color/car_body2</item>
-    </style>
-
-    <style name="CarBody2.Dark">
-        <item name="textColor">@color/car_body2_dark</item>
-    </style>
-    <style name="CarBody2.Light">
-        <item name="textColor">@color/car_body2_light</item>
-    </style>
-
-    <style name="CarBody3">
-        <item name="textStyle">normal</item>
-        <item name="textSize">@dimen/car_body3_size</item>
-        <item name="textColor">@color/car_body3</item>
-    </style>
-
-    <!-- The smallest styling for body text. The color of this text changes based on the day/night
-         mode. -->
-    <style name="CarBody4">
+    <!-- The Dialog message text style-->
+    <style name="CarDialogMessageText">
         <item name="textStyle">normal</item>
         <item name="textSize">@dimen/car_body4_size</item>
         <item name="textColor">@color/car_body4</item>
     </style>
-
-    <style name="CarAction1">
-        <item name="textStyle">bold</item>
-        <item name="textSize">@dimen/car_action1_size</item>
-        <item name="textColor">@color/control_default_material</item>
-    </style>
-
-    <style name="CarAction1.Dark">
-        <item name="textColor">@color/car_highlight_dark</item>
-    </style>
-    <style name="CarAction1.Light">
-        <item name="textColor">@color/car_highlight_light</item>
-    </style>
-
-    <!-- The styling for title text. The color of this text changes based on day/night mode. -->
-    <style name="CarTitle" >
-        <item name="textStyle">bold</item>
-        <item name="textSize">@dimen/car_title2_size</item>
-        <item name="textColor">@color/car_title</item>
-    </style>
-
-    <!-- Title text that is permanently a dark color. -->
-    <style name="CarTitle.Dark" >
-        <item name="textColor">@color/car_title_dark</item>
-    </style>
-
-    <!-- Title text that is permanently a light color. -->
-    <style name="CarTitle.Light" >
-        <item name="textColor">@color/car_title_light</item>
-    </style>
-
-    <!-- Action bar -->
-    <style name="ActionBarTitle" parent="@style/Widget.DeviceDefault.TextView">
-        <item name="android:singleLine">true</item>
-        <item name="android:textAppearance">?attr/textAppearanceLarge</item>
-    </style>
-
-    <style name="ActionBarButton"
-           parent="@style/Widget.DeviceDefault.Button.Borderless.Colored">
-        <item name="android:textAppearance">@style/ActionBarButtonTextAppearance</item>
-        <!-- Button's internal horizontal padding -->
-        <item name="android:paddingStart">@*android:dimen/car_padding_3</item>
-        <item name="android:paddingEnd">@*android:dimen/car_padding_3</item>
-        <item name="android:drawablePadding">@*android:dimen/car_padding_2</item>
-        <item name="android:maxWidth">@*android:dimen/action_bar_button_max_width</item>
-    </style>
-
-    <style name="ActionBarButtonTextAppearance"
-           parent="@style/TextAppearance.DeviceDefault.Widget.Button.Borderless.Colored">
-        <item name="android:textAllCaps">false</item>
+    <!-- This style makes Dialog button text use medium font weight.  -->
+    <style name="CarDialogButtonText">
+        <item name="android:textAppearance">@style/TextAppearance.DeviceDefault.Widget.Button</item>
+        <item name="android:textColor">@color/control_default_material</item>
     </style>
 </resources>
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellCommandHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellCommandHandler.java
deleted file mode 100644
index 73fd693..0000000
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellCommandHandler.java
+++ /dev/null
@@ -1,39 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.wm.shell;
-
-import com.android.wm.shell.common.annotations.ExternalThread;
-
-import java.io.PrintWriter;
-
-/**
- * An entry point into the shell for dumping shell internal state and running adb commands.
- *
- * Use with {@code adb shell dumpsys activity service SystemUIService WMShell ...}.
- */
-@ExternalThread
-public interface ShellCommandHandler {
-    /**
-     * Dumps the shell state.
-     */
-    void dump(PrintWriter pw);
-
-    /**
-     * Handles a shell command.
-     */
-    boolean handleCommand(final String[] args, PrintWriter pw);
-}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellInit.java b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellInit.java
deleted file mode 100644
index d7010b1..0000000
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellInit.java
+++ /dev/null
@@ -1,30 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.wm.shell;
-
-import com.android.wm.shell.common.annotations.ExternalThread;
-
-/**
- * An entry point into the shell for initializing shell internal state.
- */
-@ExternalThread
-public interface ShellInit {
-    /**
-     * Initializes the shell state.
-     */
-    void init();
-}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java
index b69cbfa..40cf9a3 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java
@@ -727,31 +727,23 @@
             ActivityManager.RunningTaskInfo taskInfo1, ActivityManager.RunningTaskInfo taskInfo2) {
         if (offsetX == 0 && offsetY == 0) {
             wct.setBounds(taskInfo1.token, mBounds1);
-            wct.setAppBounds(taskInfo1.token, null);
             wct.setScreenSizeDp(taskInfo1.token,
                     SCREEN_WIDTH_DP_UNDEFINED, SCREEN_HEIGHT_DP_UNDEFINED);
 
             wct.setBounds(taskInfo2.token, mBounds2);
-            wct.setAppBounds(taskInfo2.token, null);
             wct.setScreenSizeDp(taskInfo2.token,
                     SCREEN_WIDTH_DP_UNDEFINED, SCREEN_HEIGHT_DP_UNDEFINED);
         } else {
-            mTempRect.set(taskInfo1.configuration.windowConfiguration.getBounds());
+            getBounds1(mTempRect);
             mTempRect.offset(offsetX, offsetY);
             wct.setBounds(taskInfo1.token, mTempRect);
-            mTempRect.set(taskInfo1.configuration.windowConfiguration.getAppBounds());
-            mTempRect.offset(offsetX, offsetY);
-            wct.setAppBounds(taskInfo1.token, mTempRect);
             wct.setScreenSizeDp(taskInfo1.token,
                     taskInfo1.configuration.screenWidthDp,
                     taskInfo1.configuration.screenHeightDp);
 
-            mTempRect.set(taskInfo2.configuration.windowConfiguration.getBounds());
+            getBounds2(mTempRect);
             mTempRect.offset(offsetX, offsetY);
             wct.setBounds(taskInfo2.token, mTempRect);
-            mTempRect.set(taskInfo2.configuration.windowConfiguration.getAppBounds());
-            mTempRect.offset(offsetX, offsetY);
-            wct.setAppBounds(taskInfo2.token, mTempRect);
             wct.setScreenSizeDp(taskInfo2.token,
                     taskInfo2.configuration.screenWidthDp,
                     taskInfo2.configuration.screenHeightDp);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
index 586eab0..f85f9d6 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
@@ -29,10 +29,8 @@
 import com.android.launcher3.icons.IconProvider;
 import com.android.wm.shell.RootDisplayAreaOrganizer;
 import com.android.wm.shell.RootTaskDisplayAreaOrganizer;
-import com.android.wm.shell.ShellCommandHandler;
-import com.android.wm.shell.ShellCommandHandlerImpl;
-import com.android.wm.shell.ShellInit;
-import com.android.wm.shell.ShellInitImpl;
+import com.android.wm.shell.sysui.ShellCommandHandler;
+import com.android.wm.shell.sysui.ShellInit;
 import com.android.wm.shell.ShellTaskOrganizer;
 import com.android.wm.shell.TaskViewFactory;
 import com.android.wm.shell.TaskViewFactoryController;
@@ -624,13 +622,9 @@
 
     @WMSingleton
     @Provides
-    static ShellInit provideShellInit(ShellInitImpl impl) {
-        return impl.asShellInit();
-    }
-
-    @WMSingleton
-    @Provides
-    static ShellInitImpl provideShellInitImpl(DisplayController displayController,
+    static ShellInit provideShellInitImpl(
+            ShellController shellController,
+            DisplayController displayController,
             DisplayImeController displayImeController,
             DisplayInsetsController displayInsetsController,
             DragAndDropController dragAndDropController,
@@ -648,7 +642,8 @@
             Transitions transitions,
             StartingWindowController startingWindow,
             @ShellMainThread ShellExecutor mainExecutor) {
-        return new ShellInitImpl(displayController,
+        return new ShellInit(shellController,
+                displayController,
                 displayImeController,
                 displayInsetsController,
                 dragAndDropController,
@@ -668,19 +663,10 @@
                 mainExecutor);
     }
 
-    /**
-     * Note, this is only optional because we currently pass this to the SysUI component scope and
-     * for non-primary users, we may inject a null-optional for that dependency.
-     */
     @WMSingleton
     @Provides
-    static Optional<ShellCommandHandler> provideShellCommandHandler(ShellCommandHandlerImpl impl) {
-        return Optional.of(impl.asShellCommandHandler());
-    }
-
-    @WMSingleton
-    @Provides
-    static ShellCommandHandlerImpl provideShellCommandHandlerImpl(
+    static ShellCommandHandler provideShellCommandHandlerImpl(
+            ShellController shellController,
             ShellTaskOrganizer shellTaskOrganizer,
             KidsModeTaskOrganizer kidsModeTaskOrganizer,
             Optional<SplitScreenController> splitScreenOptional,
@@ -689,9 +675,9 @@
             Optional<HideDisplayCutoutController> hideDisplayCutout,
             Optional<RecentTasksController> recentTasksOptional,
             @ShellMainThread ShellExecutor mainExecutor) {
-        return new ShellCommandHandlerImpl(shellTaskOrganizer, kidsModeTaskOrganizer,
-                splitScreenOptional, pipOptional, oneHandedOptional, hideDisplayCutout,
-                recentTasksOptional, mainExecutor);
+        return new ShellCommandHandler(shellController, shellTaskOrganizer,
+                kidsModeTaskOrganizer, splitScreenOptional, pipOptional, oneHandedOptional,
+                hideDisplayCutout, recentTasksOptional, mainExecutor);
     }
 
     @WMSingleton
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedController.java
index c672476..24f02ac 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedController.java
@@ -681,9 +681,12 @@
             return;
         }
 
+        if (mState.getState() == STATE_ACTIVE) {
+            mOneHandedUiEventLogger.writeEvent(
+                    OneHandedUiEventLogger.EVENT_ONE_HANDED_TRIGGER_ROTATION_OUT);
+        }
+
         mDisplayAreaOrganizer.onRotateDisplay(mContext, toRotation, wct);
-        mOneHandedUiEventLogger.writeEvent(
-                OneHandedUiEventLogger.EVENT_ONE_HANDED_TRIGGER_ROTATION_OUT);
     }
 
     /**
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellCommandHandlerImpl.java b/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellCommandHandler.java
similarity index 82%
rename from libs/WindowManager/Shell/src/com/android/wm/shell/ShellCommandHandlerImpl.java
rename to libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellCommandHandler.java
index c5f7c19..0427efb 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellCommandHandlerImpl.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellCommandHandler.java
@@ -14,10 +14,11 @@
  * limitations under the License.
  */
 
-package com.android.wm.shell;
+package com.android.wm.shell.sysui;
 
 import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT;
 
+import com.android.wm.shell.ShellTaskOrganizer;
 import com.android.wm.shell.common.ShellExecutor;
 import com.android.wm.shell.hidedisplaycutout.HideDisplayCutoutController;
 import com.android.wm.shell.kidsmode.KidsModeTaskOrganizer;
@@ -34,8 +35,8 @@
  *
  * Use with {@code adb shell dumpsys activity service SystemUIService WMShell ...}.
  */
-public final class ShellCommandHandlerImpl {
-    private static final String TAG = ShellCommandHandlerImpl.class.getSimpleName();
+public final class ShellCommandHandler {
+    private static final String TAG = ShellCommandHandler.class.getSimpleName();
 
     private final Optional<SplitScreenController> mSplitScreenOptional;
     private final Optional<Pip> mPipOptional;
@@ -45,9 +46,9 @@
     private final ShellTaskOrganizer mShellTaskOrganizer;
     private final KidsModeTaskOrganizer mKidsModeTaskOrganizer;
     private final ShellExecutor mMainExecutor;
-    private final HandlerImpl mImpl = new HandlerImpl();
 
-    public ShellCommandHandlerImpl(
+    public ShellCommandHandler(
+            ShellController shellController,
             ShellTaskOrganizer shellTaskOrganizer,
             KidsModeTaskOrganizer kidsModeTaskOrganizer,
             Optional<SplitScreenController> splitScreenOptional,
@@ -64,14 +65,12 @@
         mOneHandedOptional = oneHandedOptional;
         mHideDisplayCutout = hideDisplayCutout;
         mMainExecutor = mainExecutor;
-    }
-
-    public ShellCommandHandler asShellCommandHandler() {
-        return mImpl;
+        // TODO(238217847): To be removed once the command handler dependencies are inverted
+        shellController.setShellCommandHandler(this);
     }
 
     /** Dumps WM Shell internal state. */
-    private void dump(PrintWriter pw) {
+    public void dump(PrintWriter pw) {
         mShellTaskOrganizer.dump(pw, "");
         pw.println();
         pw.println();
@@ -91,7 +90,7 @@
 
 
     /** Returns {@code true} if command was found and executed. */
-    private boolean handleCommand(final String[] args, PrintWriter pw) {
+    public boolean handleCommand(final String[] args, PrintWriter pw) {
         if (args.length < 2) {
             // Argument at position 0 is "WMShell".
             return false;
@@ -164,28 +163,4 @@
         pw.println("    Sets the position of the side-stage.");
         return true;
     }
-
-    private class HandlerImpl implements ShellCommandHandler {
-        @Override
-        public void dump(PrintWriter pw) {
-            try {
-                mMainExecutor.executeBlocking(() -> ShellCommandHandlerImpl.this.dump(pw));
-            } catch (InterruptedException e) {
-                throw new RuntimeException("Failed to dump the Shell in 2s", e);
-            }
-        }
-
-        @Override
-        public boolean handleCommand(String[] args, PrintWriter pw) {
-            try {
-                boolean[] result = new boolean[1];
-                mMainExecutor.executeBlocking(() -> {
-                    result[0] = ShellCommandHandlerImpl.this.handleCommand(args, pw);
-                });
-                return result[0];
-            } catch (InterruptedException e) {
-                throw new RuntimeException("Failed to handle Shell command in 2s", e);
-            }
-        }
-    }
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellController.java
index 837acec..618028c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellController.java
@@ -47,6 +47,9 @@
     private final ShellExecutor mMainExecutor;
     private final ShellInterfaceImpl mImpl = new ShellInterfaceImpl();
 
+    private ShellInit mShellInit;
+    private ShellCommandHandler mShellCommandHandler;
+
     private final CopyOnWriteArrayList<ConfigurationChangeListener> mConfigChangeListeners =
             new CopyOnWriteArrayList<>();
     private final CopyOnWriteArrayList<KeyguardChangeListener> mKeyguardChangeListeners =
@@ -66,6 +69,24 @@
     }
 
     /**
+     * Sets the init handler to call back to.
+     * TODO(238217847): This is only exposed this way until we can remove the dependencies from the
+     *                  init handler to other classes.
+     */
+    public void setShellInit(ShellInit shellInit) {
+        mShellInit = shellInit;
+    }
+
+    /**
+     * Sets the command handler to call back to.
+     * TODO(238217847): This is only exposed this way until we can remove the dependencies from the
+     *                  command handler to other classes.
+     */
+    public void setShellCommandHandler(ShellCommandHandler shellCommandHandler) {
+        mShellCommandHandler = shellCommandHandler;
+    }
+
+    /**
      * Adds a new configuration listener. The configuration change callbacks are not made in any
      * particular order.
      */
@@ -164,6 +185,38 @@
      */
     @ExternalThread
     private class ShellInterfaceImpl implements ShellInterface {
+
+        @Override
+        public void onInit() {
+            try {
+                mMainExecutor.executeBlocking(() -> mShellInit.init());
+            } catch (InterruptedException e) {
+                throw new RuntimeException("Failed to initialize the Shell in 2s", e);
+            }
+        }
+
+        @Override
+        public void dump(PrintWriter pw) {
+            try {
+                mMainExecutor.executeBlocking(() -> mShellCommandHandler.dump(pw));
+            } catch (InterruptedException e) {
+                throw new RuntimeException("Failed to dump the Shell in 2s", e);
+            }
+        }
+
+        @Override
+        public boolean handleCommand(String[] args, PrintWriter pw) {
+            try {
+                boolean[] result = new boolean[1];
+                mMainExecutor.executeBlocking(() -> {
+                    result[0] = mShellCommandHandler.handleCommand(args, pw);
+                });
+                return result[0];
+            } catch (InterruptedException e) {
+                throw new RuntimeException("Failed to handle Shell command in 2s", e);
+            }
+        }
+
         @Override
         public void onConfigurationChanged(Configuration newConfiguration) {
             mMainExecutor.execute(() ->
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellInitImpl.java b/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellInit.java
similarity index 92%
rename from libs/WindowManager/Shell/src/com/android/wm/shell/ShellInitImpl.java
rename to libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellInit.java
index 6694e44..2619b37 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellInitImpl.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellInit.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.wm.shell;
+package com.android.wm.shell.sysui;
 
 import static com.android.wm.shell.ShellTaskOrganizer.TASK_LISTENER_TYPE_FULLSCREEN;
 import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_INIT;
@@ -26,13 +26,13 @@
 import androidx.annotation.VisibleForTesting;
 
 import com.android.internal.protolog.common.ProtoLog;
+import com.android.wm.shell.ShellTaskOrganizer;
 import com.android.wm.shell.activityembedding.ActivityEmbeddingController;
 import com.android.wm.shell.bubbles.BubbleController;
 import com.android.wm.shell.common.DisplayController;
 import com.android.wm.shell.common.DisplayImeController;
 import com.android.wm.shell.common.DisplayInsetsController;
 import com.android.wm.shell.common.ShellExecutor;
-import com.android.wm.shell.common.annotations.ExternalThread;
 import com.android.wm.shell.draganddrop.DragAndDropController;
 import com.android.wm.shell.freeform.FreeformTaskListener;
 import com.android.wm.shell.fullscreen.FullscreenTaskListener;
@@ -53,8 +53,8 @@
  * The entry point implementation into the shell for initializing shell internal state.  Classes
  * which need to setup on start should inject an instance of this class and add an init callback.
  */
-public class ShellInitImpl {
-    private static final String TAG = ShellInitImpl.class.getSimpleName();
+public class ShellInit {
+    private static final String TAG = ShellInit.class.getSimpleName();
 
     private final DisplayController mDisplayController;
     private final DisplayImeController mDisplayImeController;
@@ -75,12 +75,12 @@
     private final Optional<RecentTasksController> mRecentTasks;
     private final Optional<ActivityEmbeddingController> mActivityEmbeddingOptional;
 
-    private final InitImpl mImpl = new InitImpl();
     // An ordered list of init callbacks to be made once shell is first started
     private final ArrayList<Pair<String, Runnable>> mInitCallbacks = new ArrayList<>();
     private boolean mHasInitialized;
 
-    public ShellInitImpl(
+    public ShellInit(
+            ShellController shellController,
             DisplayController displayController,
             DisplayImeController displayImeController,
             DisplayInsetsController displayInsetsController,
@@ -117,10 +117,8 @@
         mTransitions = transitions;
         mMainExecutor = mainExecutor;
         mStartingWindow = startingWindow;
-    }
-
-    public ShellInit asShellInit() {
-        return mImpl;
+        // TODO(238217847): To be removed once the init dependencies are inverted
+        shellController.setShellInit(this);
     }
 
     private void legacyInit() {
@@ -210,16 +208,4 @@
 
         mHasInitialized = true;
     }
-
-    @ExternalThread
-    private class InitImpl implements ShellInit {
-        @Override
-        public void init() {
-            try {
-                mMainExecutor.executeBlocking(ShellInitImpl.this::init);
-            } catch (InterruptedException e) {
-                throw new RuntimeException("Failed to initialize the Shell in 2s", e);
-            }
-        }
-    }
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellInterface.java b/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellInterface.java
index a15ce5d..254c253 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellInterface.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellInterface.java
@@ -18,15 +18,32 @@
 
 import android.content.res.Configuration;
 
+import java.io.PrintWriter;
+
 /**
  * General interface for notifying the Shell of common SysUI events like configuration or keyguard
  * changes.
- *
- * TODO: Move ShellInit and ShellCommandHandler into this interface
  */
 public interface ShellInterface {
 
     /**
+     * Initializes the shell state.
+     */
+    default void onInit() {}
+
+    /**
+     * Dumps the shell state.
+     */
+    default void dump(PrintWriter pw) {}
+
+    /**
+     * Handles a shell command.
+     */
+    default boolean handleCommand(final String[] args, PrintWriter pw) {
+        return false;
+    }
+
+    /**
      * Notifies the Shell that the configuration has changed.
      */
     default void onConfigurationChanged(Configuration newConfiguration) {}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellInitImplTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellInitTest.java
similarity index 93%
rename from libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellInitImplTest.java
rename to libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellInitTest.java
index ace8d36..219e5ab 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellInitImplTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellInitTest.java
@@ -38,6 +38,8 @@
 import com.android.wm.shell.recents.RecentTasksController;
 import com.android.wm.shell.splitscreen.SplitScreenController;
 import com.android.wm.shell.startingsurface.StartingWindowController;
+import com.android.wm.shell.sysui.ShellController;
+import com.android.wm.shell.sysui.ShellInit;
 import com.android.wm.shell.transition.Transitions;
 import com.android.wm.shell.unfold.UnfoldAnimationController;
 import com.android.wm.shell.unfold.UnfoldTransitionHandler;
@@ -54,8 +56,9 @@
 @SmallTest
 @RunWith(AndroidTestingRunner.class)
 @TestableLooper.RunWithLooper(setAsMainLooper = true)
-public class ShellInitImplTest extends ShellTestCase {
+public class ShellInitTest extends ShellTestCase {
 
+    @Mock private ShellController mShellController;
     @Mock private DisplayController mDisplayController;
     @Mock private DisplayImeController mDisplayImeController;
     @Mock private DisplayInsetsController mDisplayInsetsController;
@@ -75,12 +78,12 @@
     @Mock private StartingWindowController mStartingWindow;
     @Mock private ShellExecutor mMainExecutor;
 
-    private ShellInitImpl mImpl;
+    private ShellInit mImpl;
 
     @Before
     public void setUp() {
         MockitoAnnotations.initMocks(this);
-        mImpl = new ShellInitImpl(mDisplayController, mDisplayImeController,
+        mImpl = new ShellInit(mShellController, mDisplayController, mDisplayImeController,
                 mDisplayInsetsController, mDragAndDropController, mShellTaskOrganizer,
                 mKidsModeTaskOrganizer, mBubblesOptional, mSplitScreenOptional,
                 mPipTouchHandlerOptional, mFullscreenTaskListener, mUnfoldAnimationController,
diff --git a/packages/SettingsLib/LayoutPreference/res/layout/settings_entity_header.xml b/packages/SettingsLib/LayoutPreference/res/layout/settings_entity_header.xml
index 50f69d1..c629d96 100644
--- a/packages/SettingsLib/LayoutPreference/res/layout/settings_entity_header.xml
+++ b/packages/SettingsLib/LayoutPreference/res/layout/settings_entity_header.xml
@@ -71,6 +71,8 @@
         <TextView
             android:id="@+id/entity_header_second_summary"
             style="@style/TextAppearance.EntityHeaderSummary"
+            android:singleLine="false"
+            android:maxLines="4"
             android:layout_width="wrap_content"
             android:layout_height="wrap_content"/>
 
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 dbdbdf6..2f36ab9 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/DialogLaunchAnimator.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/DialogLaunchAnimator.kt
@@ -515,11 +515,20 @@
         dialogContentWithBackground.setTransitionVisibility(View.INVISIBLE)
 
         // Make sure the dialog is visible instantly and does not do any window animation.
-        window.attributes.windowAnimations = R.style.Animation_LaunchAnimation
+        val attributes = window.attributes
+        attributes.windowAnimations = R.style.Animation_LaunchAnimation
 
         // Ensure that the animation is not clipped by the display cut-out when animating this
         // dialog into an app.
-        window.attributes.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS
+        attributes.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS
+
+        // Ensure that the animation is not clipped by the navigation/task bars when animating this
+        // dialog into an app.
+        val wasFittingNavigationBars =
+            attributes.fitInsetsTypes and WindowInsets.Type.navigationBars() != 0
+        attributes.fitInsetsTypes =
+            attributes.fitInsetsTypes and WindowInsets.Type.navigationBars().inv()
+
         window.attributes = window.attributes
 
         // We apply the insets ourselves to make sure that the paddings are set on the correct
@@ -527,7 +536,13 @@
         window.setDecorFitsSystemWindows(false)
         val viewWithInsets = (dialogContentWithBackground.parent as ViewGroup)
         viewWithInsets.setOnApplyWindowInsetsListener { view, windowInsets ->
-            val insets = windowInsets.getInsets(WindowInsets.Type.displayCutout())
+            val type = if (wasFittingNavigationBars) {
+                WindowInsets.Type.displayCutout() or WindowInsets.Type.navigationBars()
+            } else {
+                WindowInsets.Type.displayCutout()
+            }
+
+            val insets = windowInsets.getInsets(type)
             view.setPadding(insets.left, insets.top, insets.right, insets.bottom)
             WindowInsets.CONSUMED
         }
diff --git a/packages/SystemUI/res/layout/combined_qs_header.xml b/packages/SystemUI/res/layout/combined_qs_header.xml
index ec82ccf..5dc34b9 100644
--- a/packages/SystemUI/res/layout/combined_qs_header.xml
+++ b/packages/SystemUI/res/layout/combined_qs_header.xml
@@ -14,7 +14,7 @@
   ~ See the License for the specific language governing permissions and
   ~ limitations under the License.
   -->
-<androidx.constraintlayout.motion.widget.MotionLayout
+<com.android.systemui.util.NoRemeasureMotionLayout
     xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:app="http://schemas.android.com/apk/res-auto"
     android:id="@+id/split_shade_status_bar"
@@ -32,10 +32,37 @@
     <androidx.constraintlayout.widget.Guideline
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
-        android:id="@+id/center"
-        app:layout_constraintGuide_percent="0.5"
+        android:id="@+id/begin_guide"
+        android:orientation="vertical"
+        app:layout_constraintGuide_begin="0dp"/>
+
+    <androidx.constraintlayout.widget.Guideline
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:id="@+id/end_guide"
+        android:orientation="vertical"
+        app:layout_constraintGuide_end="0dp"
+        />
+
+    <androidx.constraintlayout.widget.Guideline
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:id="@+id/center_left"
         android:orientation="vertical" />
 
+    <androidx.constraintlayout.widget.Guideline
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:id="@+id/center_right"
+        android:orientation="vertical" />
+
+    <androidx.constraintlayout.widget.Barrier
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:id="@+id/barrier"
+        app:barrierDirection="start"
+        app:constraint_referenced_ids="statusIcons,privacy_container" />
+
     <com.android.systemui.statusbar.policy.Clock
         android:id="@+id/clock"
         android:layout_width="wrap_content"
@@ -44,18 +71,25 @@
         android:paddingStart="@dimen/status_bar_left_clock_starting_padding"
         android:paddingEnd="@dimen/status_bar_left_clock_end_padding"
         android:singleLine="true"
+        android:textDirection="locale"
         android:textAppearance="@style/TextAppearance.QS.Status"
+        android:transformPivotX="0sp"
+        android:transformPivotY="20sp"
+        android:scaleX="1"
+        android:scaleY="1"
     />
 
-    <com.android.systemui.statusbar.policy.DateView
+    <com.android.systemui.statusbar.policy.VariableDateView
         android:id="@+id/date"
         android:layout_width="wrap_content"
         android:layout_height="0dp"
         android:layout_gravity="start|center_vertical"
         android:gravity="center_vertical"
         android:singleLine="true"
+        android:textDirection="locale"
         android:textAppearance="@style/TextAppearance.QS.Status"
-        app:datePattern="@string/abbrev_wday_month_day_no_year_alarm"
+        app:longDatePattern="@string/abbrev_wday_month_day_no_year_alarm"
+        app:shortDatePattern="@string/abbrev_month_day_no_year"
     />
 
     <include
@@ -81,7 +115,7 @@
         app:layout_constraintHeight_min="@dimen/large_screen_shade_header_min_height"
         android:paddingEnd="@dimen/signal_cluster_battery_padding"
         android:layout_width="wrap_content"
-        android:layout_height="48dp"
+        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"
@@ -92,8 +126,9 @@
     <com.android.systemui.battery.BatteryMeterView
         android:id="@+id/batteryRemainingIcon"
         android:layout_width="wrap_content"
-        android:layout_height="48dp"
+        android:layout_height="@dimen/large_screen_shade_header_min_height"
         app:layout_constraintHeight_min="@dimen/large_screen_shade_header_min_height"
+        app:layout_constrainedWidth="true"
         app:textAppearance="@style/TextAppearance.QS.Status"
         app:layout_constraintStart_toEndOf="@id/statusIcons"
         app:layout_constraintEnd_toEndOf="parent"
@@ -104,13 +139,18 @@
     <FrameLayout
         android:id="@+id/privacy_container"
         android:layout_width="wrap_content"
-        android:layout_height="48dp"
+        android:layout_height="@dimen/large_screen_shade_header_min_height"
         android:gravity="center"
-        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintEnd_toEndOf="@id/end_guide"
         app:layout_constraintTop_toTopOf="@id/date"
         app:layout_constraintBottom_toBottomOf="@id/date"
         >
         <include layout="@layout/ongoing_privacy_chip"/>
     </FrameLayout>
 
-</androidx.constraintlayout.motion.widget.MotionLayout>
\ No newline at end of file
+    <Space
+        android:layout_width="0dp"
+        android:layout_height="0dp"
+        android:id="@+id/space"
+    />
+</com.android.systemui.util.NoRemeasureMotionLayout>
\ No newline at end of file
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 022a6b2..0cafa35 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -553,7 +553,7 @@
     <dimen name="qs_dual_tile_padding_horizontal">6dp</dimen>
     <dimen name="qs_panel_elevation">4dp</dimen>
     <dimen name="qs_panel_padding_bottom">@dimen/footer_actions_height</dimen>
-    <dimen name="qs_panel_padding_top">48dp</dimen>
+    <dimen name="qs_panel_padding_top">80dp</dimen>
 
     <dimen name="qs_data_usage_text_size">14sp</dimen>
     <dimen name="qs_data_usage_usage_text_size">36sp</dimen>
@@ -564,6 +564,8 @@
     <dimen name="qs_footer_icon_size">20dp</dimen>
     <dimen name="qs_header_row_min_height">48dp</dimen>
 
+    <dimen name="qs_header_non_clickable_element_height">24dp</dimen>
+
     <dimen name="qs_footer_padding">20dp</dimen>
     <dimen name="qs_security_footer_height">88dp</dimen>
     <dimen name="qs_security_footer_single_line_height">48dp</dimen>
diff --git a/packages/SystemUI/res/xml/combined_qs_header_scene.xml b/packages/SystemUI/res/xml/combined_qs_header_scene.xml
index 0e83326..0fac76d 100644
--- a/packages/SystemUI/res/xml/combined_qs_header_scene.xml
+++ b/packages/SystemUI/res/xml/combined_qs_header_scene.xml
@@ -25,20 +25,109 @@
         <KeyFrameSet>
             <!-- These positions are to prevent visual movement of @id/date -->
             <KeyPosition
-                app:keyPositionType="pathRelative"
+                app:keyPositionType="deltaRelative"
                 app:percentX="0"
+                app:percentY="0"
                 app:framePosition="49"
+                app:percentWidth="1"
+                app:percentHeight="1"
+                app:curveFit="linear"
                 app:motionTarget="@id/date" />
             <KeyPosition
-                app:keyPositionType="pathRelative"
+                app:keyPositionType="deltaRelative"
                 app:percentX="1"
+                app:percentY="0.51"
                 app:framePosition="51"
+                app:percentWidth="1"
+                app:percentHeight="1"
+                app:curveFit="linear"
                 app:motionTarget="@id/date" />
             <KeyAttribute
                 app:motionTarget="@id/date"
-                app:framePosition="50"
+                app:framePosition="30"
                 android:alpha="0"
                 />
+            <KeyAttribute
+                app:motionTarget="@id/date"
+                app:framePosition="70"
+                android:alpha="0"
+                />
+            <KeyPosition
+                app:keyPositionType="pathRelative"
+                app:percentX="0"
+                app:percentY="0"
+                app:framePosition="0"
+                app:curveFit="linear"
+                app:motionTarget="@id/statusIcons" />
+            <KeyPosition
+                app:keyPositionType="pathRelative"
+                app:percentX="0"
+                app:percentY="0"
+                app:framePosition="50"
+                app:curveFit="linear"
+                app:motionTarget="@id/statusIcons" />
+            <KeyPosition
+                app:keyPositionType="deltaRelative"
+                app:percentX="1"
+                app:percentY="0.51"
+                app:framePosition="51"
+                app:curveFit="linear"
+                app:motionTarget="@id/statusIcons" />
+            <KeyAttribute
+                app:motionTarget="@id/statusIcons"
+                app:framePosition="30"
+                android:alpha="0"
+                />
+            <KeyAttribute
+                app:motionTarget="@id/statusIcons"
+                app:framePosition="70"
+                android:alpha="0"
+                />
+            <KeyPosition
+                app:keyPositionType="deltaRelative"
+                app:percentX="0"
+                app:percentY="0"
+                app:framePosition="50"
+                app:percentWidth="1"
+                app:percentHeight="1"
+                app:curveFit="linear"
+                app:motionTarget="@id/batteryRemainingIcon" />
+            <KeyPosition
+                app:keyPositionType="deltaRelative"
+                app:percentX="1"
+                app:percentY="0.51"
+                app:framePosition="51"
+                app:percentWidth="1"
+                app:percentHeight="1"
+                app:curveFit="linear"
+                app:motionTarget="@id/batteryRemainingIcon" />
+            <KeyAttribute
+                app:motionTarget="@id/batteryRemainingIcon"
+                app:framePosition="30"
+                android:alpha="0"
+                />
+            <KeyAttribute
+                app:motionTarget="@id/batteryRemainingIcon"
+                app:framePosition="70"
+                android:alpha="0"
+                />
+            <KeyPosition
+                app:motionTarget="@id/carrier_group"
+                app:percentX="1"
+                app:percentY="0.51"
+                app:framePosition="51"
+                app:percentWidth="1"
+                app:percentHeight="1"
+                app:curveFit="linear"
+                app:keyPositionType="deltaRelative" />
+            <KeyAttribute
+                app:motionTarget="@id/carrier_group"
+                app:framePosition="0"
+                android:alpha="0" />
+            <KeyAttribute
+                app:motionTarget="@id/carrier_group"
+                app:framePosition="70"
+                android:alpha="0" />
         </KeyFrameSet>
     </Transition>
 
diff --git a/packages/SystemUI/res/xml/large_screen_shade_header.xml b/packages/SystemUI/res/xml/large_screen_shade_header.xml
index 8909051..cdbf8ab 100644
--- a/packages/SystemUI/res/xml/large_screen_shade_header.xml
+++ b/packages/SystemUI/res/xml/large_screen_shade_header.xml
@@ -29,7 +29,12 @@
             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"
+            />
     </Constraint>
 
     <Constraint
@@ -47,9 +52,38 @@
 
     <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:customFloatValue="1"
+        />
+    </Constraint>
+
+    <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"
         />
     </Constraint>
 
@@ -64,6 +98,9 @@
             app:layout_constraintTop_toTopOf="@id/clock"
             app:layout_constraintBottom_toBottomOf="parent"
         />
+        <PropertySet
+            android:alpha="1"
+        />
     </Constraint>
 
     <Constraint
@@ -75,6 +112,7 @@
             app:layout_constraintTop_toTopOf="@id/date"
             app:layout_constraintBottom_toBottomOf="@id/date"
             app:layout_constraintStart_toEndOf="@id/batteryRemainingIcon"
+            app:layout_constraintHorizontal_bias="1"
         />
     </Constraint>
 
diff --git a/packages/SystemUI/res/xml/qqs_header.xml b/packages/SystemUI/res/xml/qqs_header.xml
index c5b4c5d..ee0c4fb6 100644
--- a/packages/SystemUI/res/xml/qqs_header.xml
+++ b/packages/SystemUI/res/xml/qqs_header.xml
@@ -26,22 +26,27 @@
         <Layout
             android:layout_width="wrap_content"
             android:layout_height="0dp"
-            app:layout_constraintStart_toStartOf="parent"
+            app:layout_constraintStart_toStartOf="@id/begin_guide"
             app:layout_constraintTop_toTopOf="parent"
             app:layout_constraintBottom_toBottomOf="parent"
             app:layout_constraintEnd_toStartOf="@id/date"
             app:layout_constraintHorizontal_bias="0"
             app:layout_constraintHorizontal_chainStyle="packed"
         />
+        <Transform
+            android:scaleX="1"
+            android:scaleY="1"
+            />
     </Constraint>
 
     <Constraint
         android:id="@+id/date">
         <Layout
-            android:layout_width="wrap_content"
+            android:layout_width="0dp"
             android:layout_height="0dp"
+            app:layout_constrainedWidth="true"
             app:layout_constraintStart_toEndOf="@id/clock"
-            app:layout_constraintEnd_toStartOf="@id/carrier_group"
+            app:layout_constraintEnd_toStartOf="@id/barrier"
             app:layout_constraintTop_toTopOf="parent"
             app:layout_constraintBottom_toBottomOf="parent"
             app:layout_constraintHorizontal_bias="0"
@@ -50,17 +55,41 @@
 
     <Constraint
         android:id="@+id/statusIcons">
+        <Layout
+            android:layout_width="0dp"
+            android:layout_height="@dimen/qs_header_non_clickable_element_height"
+            app:layout_constraintHeight_min="@dimen/qs_header_non_clickable_element_height"
+            app:layout_constraintStart_toEndOf="@id/date"
+            app:layout_constraintEnd_toStartOf="@id/batteryRemainingIcon"
+            app:layout_constraintTop_toTopOf="@id/date"
+            app:layout_constraintBottom_toBottomOf="parent"
+            app:layout_constraintHorizontal_bias="1"
+            />
     </Constraint>
 
     <Constraint
-        android:id="@+id/batteryRemainingIcon" >
+        android:id="@+id/batteryRemainingIcon">
+        <Layout
+            android:layout_width="wrap_content"
+            android:layout_height="@dimen/qs_header_non_clickable_element_height"
+            app:layout_constrainedWidth="true"
+            app:layout_constraintHeight_min="@dimen/qs_header_non_clickable_element_height"
+            app:layout_constraintStart_toEndOf="@id/statusIcons"
+            app:layout_constraintEnd_toEndOf="@id/end_guide"
+            app:layout_constraintTop_toTopOf="@id/date"
+            app:layout_constraintBottom_toBottomOf="parent"
+            app:layout_constraintHorizontal_bias="1"
+            />
     </Constraint>
 
     <Constraint
         android:id="@+id/carrier_group">
-        <CustomAttribute
-            app:attributeName="alpha"
-            app:customFloatValue="0"
+        <Layout
+            app:layout_constraintEnd_toEndOf="parent"
+            app:layout_constraintTop_toTopOf="parent"
+            />
+        <PropertySet
+            android:alpha="0"
         />
     </Constraint>
 
@@ -69,9 +98,11 @@
         <Layout
             android:layout_width="wrap_content"
             android:layout_height="0dp"
-            app:layout_constraintEnd_toEndOf="parent"
+            app:layout_constraintStart_toEndOf="@id/date"
+            app:layout_constraintEnd_toEndOf="@id/end_guide"
             app:layout_constraintTop_toTopOf="@id/date"
             app:layout_constraintBottom_toBottomOf="@id/date"
+            app:layout_constraintHorizontal_bias="1"
         />
     </Constraint>
 
diff --git a/packages/SystemUI/res/xml/qs_header_new.xml b/packages/SystemUI/res/xml/qs_header_new.xml
new file mode 100644
index 0000000..f39e6bd
--- /dev/null
+++ b/packages/SystemUI/res/xml/qs_header_new.xml
@@ -0,0 +1,124 @@
+<?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");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<ConstraintSet
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    android:id="@+id/qs_header_constraint"
+>
+
+    <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="@id/end_guide"
+            app:layout_constraintTop_toTopOf="parent"
+            app:layout_constraintBottom_toTopOf="@id/carrier_group"
+            app:layout_constraintHorizontal_bias="1"
+            />
+    </Constraint>
+
+    <Constraint
+        android:id="@+id/clock">
+        <Layout
+            android:layout_width="wrap_content"
+            android:layout_height="@dimen/large_screen_shade_header_min_height"
+            app:layout_constraintStart_toStartOf="parent"
+            app:layout_constraintTop_toBottomOf="@id/privacy_container"
+            app:layout_constraintBottom_toTopOf="@id/date"
+            app:layout_constraintEnd_toStartOf="@id/carrier_group"
+            app:layout_constraintHorizontal_bias="0"
+        />
+        <Transform
+            android:scaleX="2.4"
+            android:scaleY="2.4"
+            />
+    </Constraint>
+
+    <Constraint
+        android:id="@+id/date">
+        <Layout
+            android:layout_width="0dp"
+            android:layout_height="@dimen/qs_header_non_clickable_element_height"
+            app:layout_constraintStart_toStartOf="parent"
+            app:layout_constraintEnd_toStartOf="@id/space"
+            app:layout_constraintBottom_toBottomOf="parent"
+            app:layout_constraintTop_toBottomOf="@id/clock"
+            app:layout_constraintHorizontal_bias="0"
+            app:layout_constraintHorizontal_chainStyle="spread_inside"
+        />
+    </Constraint>
+
+    <Constraint
+        android:id="@+id/carrier_group">
+        <Layout
+            app:layout_constraintHeight_min="@dimen/large_screen_shade_header_min_height"
+            android:minHeight="@dimen/large_screen_shade_header_min_height"
+            app:layout_constraintWidth_min="48dp"
+            android:layout_width="0dp"
+            android:layout_height="0dp"
+            app:layout_constraintStart_toEndOf="@id/clock"
+            app:layout_constraintTop_toBottomOf="@id/privacy_container"
+            app:layout_constraintEnd_toEndOf="parent"
+            app:layout_constraintHorizontal_bias="1"
+            app:layout_constraintBottom_toTopOf="@id/batteryRemainingIcon"
+            />
+        <PropertySet
+            android:alpha="1"
+            />
+    </Constraint>
+
+    <Constraint
+        android:id="@+id/statusIcons">
+        <Layout
+            android:layout_width="0dp"
+            android:layout_height="@dimen/qs_header_non_clickable_element_height"
+            app:layout_constrainedWidth="true"
+            app:layout_constraintStart_toEndOf="@id/space"
+            app:layout_constraintEnd_toStartOf="@id/batteryRemainingIcon"
+            app:layout_constraintTop_toTopOf="@id/date"
+            app:layout_constraintBottom_toBottomOf="parent"
+            app:layout_constraintHorizontal_bias="1"
+            />
+    </Constraint>
+
+    <Constraint
+        android:id="@+id/batteryRemainingIcon">
+        <Layout
+            android:layout_width="wrap_content"
+            android:layout_height="@dimen/qs_header_non_clickable_element_height"
+            app:layout_constraintHeight_min="@dimen/qs_header_non_clickable_element_height"
+            app:layout_constraintStart_toEndOf="@id/statusIcons"
+            app:layout_constraintEnd_toEndOf="parent"
+            app:layout_constraintTop_toTopOf="@id/date"
+            app:layout_constraintBottom_toBottomOf="parent"
+            app:layout_constraintHorizontal_bias="1"
+            />
+    </Constraint>
+
+
+    <Constraint
+        android:id="@id/space">
+        <Layout
+            android:layout_width="0dp"
+            android:layout_height="0dp"
+            app:layout_constraintStart_toEndOf="@id/date"
+            app:layout_constraintEnd_toStartOf="@id/statusIcons"
+            />
+    </Constraint>
+</ConstraintSet>
\ No newline at end of file
diff --git a/packages/SystemUI/shared/src/com/android/systemui/flags/Flag.kt b/packages/SystemUI/shared/src/com/android/systemui/flags/Flag.kt
index b4646ae..a8a526a 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/flags/Flag.kt
+++ b/packages/SystemUI/shared/src/com/android/systemui/flags/Flag.kt
@@ -29,6 +29,7 @@
 
 interface ParcelableFlag<T> : Flag<T>, Parcelable {
     val default: T
+    val overridden: Boolean
     override fun describeContents() = 0
 }
 
@@ -52,7 +53,8 @@
 data class BooleanFlag @JvmOverloads constructor(
     override val id: Int,
     override val default: Boolean = false,
-    override val teamfood: Boolean = false
+    override val teamfood: Boolean = false,
+    override val overridden: Boolean = false
 ) : ParcelableFlag<Boolean> {
 
     companion object {
@@ -65,12 +67,16 @@
 
     private constructor(parcel: Parcel) : this(
         id = parcel.readInt(),
-        default = parcel.readBoolean()
+        default = parcel.readBoolean(),
+        teamfood = parcel.readBoolean(),
+        overridden = parcel.readBoolean()
     )
 
     override fun writeToParcel(parcel: Parcel, flags: Int) {
         parcel.writeInt(id)
         parcel.writeBoolean(default)
+        parcel.writeBoolean(teamfood)
+        parcel.writeBoolean(overridden)
     }
 }
 
@@ -100,7 +106,8 @@
 data class StringFlag @JvmOverloads constructor(
     override val id: Int,
     override val default: String = "",
-    override val teamfood: Boolean = false
+    override val teamfood: Boolean = false,
+    override val overridden: Boolean = false
 ) : ParcelableFlag<String> {
     companion object {
         @JvmField
@@ -130,7 +137,8 @@
 data class IntFlag @JvmOverloads constructor(
     override val id: Int,
     override val default: Int = 0,
-    override val teamfood: Boolean = false
+    override val teamfood: Boolean = false,
+    override val overridden: Boolean = false
 ) : ParcelableFlag<Int> {
 
     companion object {
@@ -161,7 +169,8 @@
 data class LongFlag @JvmOverloads constructor(
     override val id: Int,
     override val default: Long = 0,
-    override val teamfood: Boolean = false
+    override val teamfood: Boolean = false,
+    override val overridden: Boolean = false
 ) : ParcelableFlag<Long> {
 
     companion object {
@@ -186,7 +195,8 @@
 data class FloatFlag @JvmOverloads constructor(
     override val id: Int,
     override val default: Float = 0f,
-    override val teamfood: Boolean = false
+    override val teamfood: Boolean = false,
+    override val overridden: Boolean = false
 ) : ParcelableFlag<Float> {
 
     companion object {
@@ -217,7 +227,8 @@
 data class DoubleFlag @JvmOverloads constructor(
     override val id: Int,
     override val default: Double = 0.0,
-    override val teamfood: Boolean = false
+    override val teamfood: Boolean = false,
+    override val overridden: Boolean = false
 ) : ParcelableFlag<Double> {
 
     companion object {
@@ -237,4 +248,4 @@
         parcel.writeInt(id)
         parcel.writeDouble(default)
     }
-}
\ No newline at end of file
+}
diff --git a/packages/SystemUI/shared/src/com/android/systemui/flags/FlagManager.kt b/packages/SystemUI/shared/src/com/android/systemui/flags/FlagManager.kt
index 26e40e1..d172690 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/flags/FlagManager.kt
+++ b/packages/SystemUI/shared/src/com/android/systemui/flags/FlagManager.kt
@@ -64,20 +64,29 @@
         intent.setPackage(RECEIVING_PACKAGE)
 
         return CallbackToFutureAdapter.getFuture {
-            completer: CallbackToFutureAdapter.Completer<Collection<Flag<*>>> ->
-                context.sendOrderedBroadcast(intent, null,
-                    object : BroadcastReceiver() {
-                        override fun onReceive(context: Context, intent: Intent) {
-                            val extras: Bundle? = getResultExtras(false)
-                            val listOfFlags: java.util.ArrayList<ParcelableFlag<*>>? =
-                                extras?.getParcelableArrayList(EXTRA_FLAGS)
-                            if (listOfFlags != null) {
-                                completer.set(listOfFlags)
-                            } else {
-                                completer.setException(NoFlagResultsException())
-                            }
+                completer: CallbackToFutureAdapter.Completer<Collection<Flag<*>>> ->
+            context.sendOrderedBroadcast(
+                intent,
+                null,
+                object : BroadcastReceiver() {
+                    override fun onReceive(context: Context, intent: Intent) {
+                        val extras: Bundle? = getResultExtras(false)
+                        val listOfFlags: java.util.ArrayList<ParcelableFlag<*>>? =
+                            extras?.getParcelableArrayList(
+                                EXTRA_FLAGS, ParcelableFlag::class.java
+                            )
+                        if (listOfFlags != null) {
+                            completer.set(listOfFlags)
+                        } else {
+                            completer.setException(NoFlagResultsException())
                         }
-                    }, null, Activity.RESULT_OK, "extra data", null)
+                    }
+                },
+                null,
+                Activity.RESULT_OK,
+                "extra data",
+                null
+            )
             "QueryingFlags"
         }
     }
@@ -152,7 +161,11 @@
             }
             val parts = uri.pathSegments
             val idStr = parts[parts.size - 1]
-            val id = try { idStr.toInt() } catch (e: NumberFormatException) { return }
+            val id = try {
+                idStr.toInt()
+            } catch (e: NumberFormatException) {
+                return
+            }
             clearCacheAction?.accept(id)
             dispatchListenersAndMaybeRestart(id, onSettingsChangedAction)
         }
@@ -188,4 +201,5 @@
 }
 
 class NoFlagResultsException : Exception(
-    "SystemUI failed to communicate its flags back successfully")
\ No newline at end of file
+    "SystemUI failed to communicate its flags back successfully"
+)
diff --git a/packages/SystemUI/src/com/android/keyguard/AnimatableClockView.kt b/packages/SystemUI/src/com/android/keyguard/AnimatableClockView.kt
index e22386e..b4955d2 100644
--- a/packages/SystemUI/src/com/android/keyguard/AnimatableClockView.kt
+++ b/packages/SystemUI/src/com/android/keyguard/AnimatableClockView.kt
@@ -257,6 +257,8 @@
         )
     }
 
+    private val glyphFilter: GlyphCallback? = null // Add text animation tweak here.
+
     /**
      * Set text style with an optional animation.
      *
@@ -288,6 +290,7 @@
                 delay = delay,
                 onAnimationEnd = onAnimationEnd
             )
+            textAnimator?.glyphFilter = glyphFilter
         } else {
             // when the text animator is set, update its start values
             onTextAnimatorInitialized = Runnable {
@@ -301,6 +304,7 @@
                     delay = delay,
                     onAnimationEnd = onAnimationEnd
                 )
+                textAnimator?.glyphFilter = glyphFilter
             }
         }
     }
diff --git a/packages/SystemUI/src/com/android/keyguard/TextAnimator.kt b/packages/SystemUI/src/com/android/keyguard/TextAnimator.kt
index 3361015..ade89af 100644
--- a/packages/SystemUI/src/com/android/keyguard/TextAnimator.kt
+++ b/packages/SystemUI/src/com/android/keyguard/TextAnimator.kt
@@ -22,12 +22,14 @@
 import android.animation.ValueAnimator
 import android.graphics.Canvas
 import android.graphics.Typeface
+import android.graphics.fonts.Font
 import android.text.Layout
 import android.util.SparseArray
 
 private const val TAG_WGHT = "wght"
 private const val DEFAULT_ANIMATION_DURATION: Long = 300
 
+typealias GlyphCallback = (TextAnimator.PositionedGlyph, Float) -> Unit
 /**
  * This class provides text animation between two styles.
  *
@@ -74,6 +76,59 @@
         })
     }
 
+    sealed class PositionedGlyph {
+
+        /**
+         * Mutable X coordinate of the glyph position relative from drawing offset.
+         */
+        var x: Float = 0f
+
+        /**
+         * Mutable Y coordinate of the glyph position relative from the baseline.
+         */
+        var y: Float = 0f
+
+        /**
+         * Mutable text size of the glyph in pixels.
+         */
+        var textSize: Float = 0f
+
+        /**
+         * Mutable color of the glyph.
+         */
+        var color: Int = 0
+
+        /**
+         * Immutable character offset in the text that the current font run start.
+         */
+        abstract var runStart: Int
+            protected set
+
+        /**
+         * Immutable run length of the font run.
+         */
+        abstract var runLength: Int
+            protected set
+
+        /**
+         * Immutable glyph index of the font run.
+         */
+        abstract var glyphIndex: Int
+            protected set
+
+        /**
+         * Immutable font instance for this font run.
+         */
+        abstract var font: Font
+            protected set
+
+        /**
+         * Immutable glyph ID for this glyph.
+         */
+        abstract var glyphId: Int
+            protected set
+    }
+
     private val typefaceCache = SparseArray<Typeface?>()
 
     fun updateLayout(layout: Layout) {
@@ -84,6 +139,57 @@
         return animator.isRunning
     }
 
+    /**
+     * GlyphFilter applied just before drawing to canvas for tweaking positions and text size.
+     *
+     * This callback is called for each glyphs just before drawing the glyphs. This function will
+     * be called with the intrinsic position, size, color, glyph ID and font instance. You can
+     * mutate the position, size and color for tweaking animations.
+     * Do not keep the reference of passed glyph object. The interpolator reuses that object for
+     * avoiding object allocations.
+     *
+     * Details:
+     * The text is drawn with font run units. The font run is a text segment that draws with the
+     * same font. The {@code runStart} and {@code runLimit} is a range of the font run in the text
+     * that current glyph is in. Once the font run is determined, the system will convert characters
+     * into glyph IDs. The {@code glyphId} is the glyph identifier in the font and
+     * {@code glyphIndex} is the offset of the converted glyph array. Please note that the
+     * {@code glyphIndex} is not a character index, because the character will not be converted to
+     * glyph one-by-one. If there are ligatures including emoji sequence, etc, the glyph ID may be
+     * composed from multiple characters.
+     *
+     * Here is an example of font runs: "fin. 終わり"
+     *
+     * Characters :    f      i      n      .      _      終     わ     り
+     * Code Points: \u0066 \u0069 \u006E \u002E \u0020 \u7D42 \u308F \u308A
+     * Font Runs  : <-- Roboto-Regular.ttf          --><-- NotoSans-CJK.otf -->
+     *                  runStart = 0, runLength = 5        runStart = 5, runLength = 3
+     * Glyph IDs  :      194        48     7      8     4367   1039   1002
+     * Glyph Index:       0          1     2      3       0      1      2
+     *
+     * In this example, the "fi" is converted into ligature form, thus the single glyph ID is
+     * assigned for two characters, f and i.
+     *
+     * Example:
+     * ```
+     * private val glyphFilter: GlyphCallback = { glyph, progress ->
+     *     val index = glyph.runStart
+     *     val i = glyph.glyphIndex
+     *     val moveAmount = 1.3f
+     *     val sign = (-1 + 2 * ((i + index) % 2))
+     *     val turnProgress = if (progress < .5f) progress / 0.5f else (1.0f - progress) / 0.5f
+     *
+     *     // You can modify (x, y) coordinates, textSize and color during animation.
+     *     glyph.textSize += glyph.textSize * sign * moveAmount * turnProgress
+     *     glyph.y += glyph.y * sign * moveAmount * turnProgress
+     *     glyph.x += glyph.x * sign * moveAmount * turnProgress
+     * }
+     * ```
+     */
+    var glyphFilter: GlyphCallback?
+        get() = textInterpolator.glyphFilter
+        set(value) { textInterpolator.glyphFilter = value }
+
     fun draw(c: Canvas) = textInterpolator.draw(c)
 
     /**
diff --git a/packages/SystemUI/src/com/android/keyguard/TextInterpolator.kt b/packages/SystemUI/src/com/android/keyguard/TextInterpolator.kt
index 5d5797c..20dbe29 100644
--- a/packages/SystemUI/src/com/android/keyguard/TextInterpolator.kt
+++ b/packages/SystemUI/src/com/android/keyguard/TextInterpolator.kt
@@ -89,8 +89,11 @@
     private var lines = listOf<Line>()
     private val fontInterpolator = FontInterpolator()
 
-    // Recycling object for glyph drawing. Will be extended for the longest font run if needed.
-    private val tmpDrawPaint = TextPaint()
+    // Recycling object for glyph drawing and tweaking.
+    private val tmpPaint = TextPaint()
+    private val tmpPaintForGlyph by lazy { TextPaint() }
+    private val tmpGlyph by lazy { MutablePositionedGlyph() }
+    // Will be extended for the longest font run if needed.
     private var tmpPositionArray = FloatArray(20)
 
     /**
@@ -206,8 +209,8 @@
         } else if (progress == 1f) {
             basePaint.set(targetPaint)
         } else {
-            lerp(basePaint, targetPaint, progress, tmpDrawPaint)
-            basePaint.set(tmpDrawPaint)
+            lerp(basePaint, targetPaint, progress, tmpPaint)
+            basePaint.set(tmpPaint)
         }
 
         lines.forEach { line ->
@@ -231,7 +234,7 @@
      * @param canvas a canvas.
      */
     fun draw(canvas: Canvas) {
-        lerp(basePaint, targetPaint, progress, tmpDrawPaint)
+        lerp(basePaint, targetPaint, progress, tmpPaint)
         lines.forEachIndexed { lineNo, line ->
             line.runs.forEach { run ->
                 canvas.save()
@@ -241,7 +244,7 @@
                     canvas.translate(origin, layout.getLineBaseline(lineNo).toFloat())
 
                     run.fontRuns.forEach { fontRun ->
-                        drawFontRun(canvas, run, fontRun, tmpDrawPaint)
+                        drawFontRun(canvas, run, fontRun, tmpPaint)
                     }
                 } finally {
                     canvas.restore()
@@ -330,24 +333,82 @@
         }
     }
 
+    private class MutablePositionedGlyph : TextAnimator.PositionedGlyph() {
+        override var runStart: Int = 0
+            public set
+        override var runLength: Int = 0
+            public set
+        override var glyphIndex: Int = 0
+            public set
+        override lateinit var font: Font
+            public set
+        override var glyphId: Int = 0
+            public set
+    }
+
+    var glyphFilter: GlyphCallback? = null
+
     // Draws single font run.
     private fun drawFontRun(c: Canvas, line: Run, run: FontRun, paint: Paint) {
         var arrayIndex = 0
+        val font = fontInterpolator.lerp(run.baseFont, run.targetFont, progress)
+
+        val glyphFilter = glyphFilter
+        if (glyphFilter == null) {
+            for (i in run.start until run.end) {
+                tmpPositionArray[arrayIndex++] =
+                        MathUtils.lerp(line.baseX[i], line.targetX[i], progress)
+                tmpPositionArray[arrayIndex++] =
+                        MathUtils.lerp(line.baseY[i], line.targetY[i], progress)
+            }
+            c.drawGlyphs(line.glyphIds, run.start, tmpPositionArray, 0, run.length, font, paint)
+            return
+        }
+
+        tmpGlyph.font = font
+        tmpGlyph.runStart = run.start
+        tmpGlyph.runLength = run.end - run.start
+
+        tmpPaintForGlyph.set(paint)
+        var prevStart = run.start
+
         for (i in run.start until run.end) {
-            tmpPositionArray[arrayIndex++] =
-                    MathUtils.lerp(line.baseX[i], line.targetX[i], progress)
-            tmpPositionArray[arrayIndex++] =
-                    MathUtils.lerp(line.baseY[i], line.targetY[i], progress)
+            tmpGlyph.glyphId = line.glyphIds[i]
+            tmpGlyph.x = MathUtils.lerp(line.baseX[i], line.targetX[i], progress)
+            tmpGlyph.y = MathUtils.lerp(line.baseY[i], line.targetY[i], progress)
+            tmpGlyph.textSize = paint.textSize
+            tmpGlyph.color = paint.color
+
+            glyphFilter(tmpGlyph, progress)
+
+            if (tmpGlyph.textSize != paint.textSize || tmpGlyph.color != paint.color) {
+                tmpPaintForGlyph.textSize = tmpGlyph.textSize
+                tmpPaintForGlyph.color = tmpGlyph.color
+
+                c.drawGlyphs(
+                        line.glyphIds,
+                        prevStart,
+                        tmpPositionArray,
+                        0,
+                        i - prevStart,
+                        font,
+                        tmpPaintForGlyph)
+                prevStart = i
+                arrayIndex = 0
+            }
+
+            tmpPositionArray[arrayIndex++] = tmpGlyph.x
+            tmpPositionArray[arrayIndex++] = tmpGlyph.y
         }
 
         c.drawGlyphs(
                 line.glyphIds,
-                run.start,
+                prevStart,
                 tmpPositionArray,
                 0,
-                run.length,
-                fontInterpolator.lerp(run.baseFont, run.targetFont, progress),
-                paint)
+                run.end - prevStart,
+                font,
+                tmpPaintForGlyph)
     }
 
     private fun updatePositionsAndFonts(
diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIInitializer.java b/packages/SystemUI/src/com/android/systemui/SystemUIInitializer.java
index 24fcf15..e9ca0fd 100644
--- a/packages/SystemUI/src/com/android/systemui/SystemUIInitializer.java
+++ b/packages/SystemUI/src/com/android/systemui/SystemUIInitializer.java
@@ -96,7 +96,6 @@
                     .setSplitScreen(mWMComponent.getSplitScreen())
                     .setOneHanded(mWMComponent.getOneHanded())
                     .setBubbles(mWMComponent.getBubbles())
-                    .setShellCommandHandler(mWMComponent.getShellCommandHandler())
                     .setTaskViewFactory(mWMComponent.getTaskViewFactory())
                     .setTransitions(mWMComponent.getTransitions())
                     .setStartingSurface(mWMComponent.getStartingSurface())
@@ -112,7 +111,6 @@
                     .setSplitScreen(Optional.ofNullable(null))
                     .setOneHanded(Optional.ofNullable(null))
                     .setBubbles(Optional.ofNullable(null))
-                    .setShellCommandHandler(Optional.ofNullable(null))
                     .setTaskViewFactory(Optional.ofNullable(null))
                     .setTransitions(new ShellTransitions() {})
                     .setDisplayAreaHelper(Optional.ofNullable(null))
diff --git a/packages/SystemUI/src/com/android/systemui/camera/CameraGestureHelper.kt b/packages/SystemUI/src/com/android/systemui/camera/CameraGestureHelper.kt
index cccd3a4..81da8023 100644
--- a/packages/SystemUI/src/com/android/systemui/camera/CameraGestureHelper.kt
+++ b/packages/SystemUI/src/com/android/systemui/camera/CameraGestureHelper.kt
@@ -36,7 +36,7 @@
 import com.android.systemui.shared.system.ActivityManagerKt.isInForeground
 import com.android.systemui.statusbar.StatusBarState
 import com.android.systemui.statusbar.phone.CentralSurfaces
-import com.android.systemui.statusbar.phone.PanelViewController
+import com.android.systemui.shade.PanelViewController
 import com.android.systemui.statusbar.policy.KeyguardStateController
 import java.util.concurrent.Executor
 import javax.inject.Inject
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java b/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java
index fd7680f..029cabb 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java
@@ -37,7 +37,6 @@
 import com.android.systemui.unfold.SysUIUnfoldComponent;
 import com.android.systemui.unfold.UnfoldLatencyTracker;
 import com.android.systemui.unfold.util.NaturalRotationUnfoldProgressProvider;
-import com.android.wm.shell.ShellCommandHandler;
 import com.android.wm.shell.TaskViewFactory;
 import com.android.wm.shell.back.BackAnimation;
 import com.android.wm.shell.bubbles.Bubbles;
@@ -96,9 +95,6 @@
         Builder setTaskViewFactory(Optional<TaskViewFactory> t);
 
         @BindsInstance
-        Builder setShellCommandHandler(Optional<ShellCommandHandler> shellDump);
-
-        @BindsInstance
         Builder setTransitions(ShellTransitions t);
 
         @BindsInstance
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/WMComponent.java b/packages/SystemUI/src/com/android/systemui/dagger/WMComponent.java
index e4c0325..78a45f9 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/WMComponent.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/WMComponent.java
@@ -23,8 +23,8 @@
 
 import com.android.systemui.SystemUIInitializerFactory;
 import com.android.systemui.tv.TvWMComponent;
-import com.android.wm.shell.ShellCommandHandler;
-import com.android.wm.shell.ShellInit;
+import com.android.wm.shell.sysui.ShellCommandHandler;
+import com.android.wm.shell.sysui.ShellInit;
 import com.android.wm.shell.TaskViewFactory;
 import com.android.wm.shell.back.BackAnimation;
 import com.android.wm.shell.bubbles.Bubbles;
@@ -75,17 +75,24 @@
      * Initializes all the WMShell components before starting any of the SystemUI components.
      */
     default void init() {
-        getShellInit().init();
+        // TODO(238217847): To be removed once the dependencies are inverted and ShellController can
+        // inject these classes directly, otherwise, it's currently needed to ensure that these
+        // classes are created and set on the controller before onInit() is called
+        getShellInit();
+        getShellCommandHandler();
+        getShell().onInit();
     }
 
     @WMSingleton
+    ShellInterface getShell();
+
+    // TODO(238217847): To be removed once ShellController can inject ShellInit directly
+    @WMSingleton
     ShellInit getShellInit();
 
+    // TODO(238217847): To be removed once ShellController can inject ShellCommandHandler directly
     @WMSingleton
-    Optional<ShellCommandHandler> getShellCommandHandler();
-
-    @WMSingleton
-    ShellInterface getShell();
+    ShellCommandHandler getShellCommandHandler();
 
     @WMSingleton
     Optional<OneHanded> getOneHanded();
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/touch/dagger/BouncerSwipeModule.java b/packages/SystemUI/src/com/android/systemui/dreams/touch/dagger/BouncerSwipeModule.java
index b9436f9..9c22dc6 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/touch/dagger/BouncerSwipeModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/touch/dagger/BouncerSwipeModule.java
@@ -25,7 +25,7 @@
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.dreams.touch.BouncerSwipeTouchHandler;
 import com.android.systemui.dreams.touch.DreamTouchHandler;
-import com.android.systemui.statusbar.phone.PanelViewController;
+import com.android.systemui.shade.PanelViewController;
 import com.android.wm.shell.animation.FlingAnimationUtils;
 
 import javax.inject.Named;
diff --git a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsDebug.java b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsDebug.java
index 49b3908..c5221cd 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsDebug.java
+++ b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsDebug.java
@@ -32,7 +32,6 @@
 import android.os.Bundle;
 import android.os.RemoteException;
 import android.os.UserHandle;
-import android.provider.DeviceConfig;
 import android.util.Log;
 
 import androidx.annotation.NonNull;
@@ -197,7 +196,7 @@
 
     /** Specific override for Boolean flags that checks against the teamfood list.*/
     private boolean readFlagValue(int id, boolean defaultValue) {
-        Boolean result = readFlagValueInternal(id, BooleanFlagSerializer.INSTANCE);
+        Boolean result = readBooleanFlagOverride(id);
         // Only check for teamfood if the default is false.
         if (!defaultValue && result == null && id != Flags.TEAMFOOD.getId()) {
             if (mAllFlags.containsKey(id) && mAllFlags.get(id).getTeamfood()) {
@@ -208,6 +207,10 @@
         return result == null ? defaultValue : result;
     }
 
+    private Boolean readBooleanFlagOverride(int id) {
+        return readFlagValueInternal(id, BooleanFlagSerializer.INSTANCE);
+    }
+
     @NonNull
     private <T> T readFlagValue(int id, @NonNull T defaultValue, FlagSerializer<T> serializer) {
         requireNonNull(defaultValue, "defaultValue");
@@ -407,11 +410,18 @@
         @Nullable
         private ParcelableFlag<?> toParcelableFlag(Flag<?> f) {
             if (f instanceof BooleanFlag) {
-                return new BooleanFlag(f.getId(), isEnabled((BooleanFlag) f), f.getTeamfood());
+                return new BooleanFlag(
+                        f.getId(),
+                        isEnabled((BooleanFlag) f),
+                        f.getTeamfood(),
+                        readBooleanFlagOverride(f.getId()) != null);
             }
             if (f instanceof ResourceBooleanFlag) {
                 return new BooleanFlag(
-                        f.getId(), isEnabled((ResourceBooleanFlag) f), f.getTeamfood());
+                        f.getId(),
+                        isEnabled((ResourceBooleanFlag) f),
+                        f.getTeamfood(),
+                        readBooleanFlagOverride(f.getId()) != null);
             }
             if (f instanceof DeviceConfigBooleanFlag) {
                 return new BooleanFlag(
@@ -420,7 +430,10 @@
             if (f instanceof SysPropBooleanFlag) {
                 // TODO(b/223379190): Teamfood not supported for sysprop flags yet.
                 return new BooleanFlag(
-                        f.getId(), isEnabled((SysPropBooleanFlag) f), false);
+                        f.getId(),
+                        ((SysPropBooleanFlag) f).getDefault(),
+                        false,
+                        !mSystemProperties.get(((SysPropBooleanFlag) f).getName()).isEmpty());
             }
 
             // TODO: add support for other flag types.
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.java b/packages/SystemUI/src/com/android/systemui/flags/Flags.java
index 245ea21..65711ed 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.java
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.java
@@ -63,6 +63,9 @@
     public static final BooleanFlag REMOVE_UNRANKED_NOTIFICATIONS =
             new BooleanFlag(109, false);
 
+    public static final BooleanFlag FSI_REQUIRES_KEYGUARD =
+            new BooleanFlag(110, false, true);
+
     /***************************************/
     // 200 - keyguard/lockscreen
 
@@ -116,7 +119,7 @@
             new BooleanFlag(500, true);
 
     public static final BooleanFlag COMBINED_QS_HEADERS =
-            new BooleanFlag(501, false);
+            new BooleanFlag(501, false, true);
 
     public static final ResourceBooleanFlag PEOPLE_TILE =
             new ResourceBooleanFlag(502, R.bool.flag_conversations);
@@ -130,7 +133,7 @@
     @Deprecated
     public static final BooleanFlag NEW_FOOTER = new BooleanFlag(504, true);
 
-    public static final BooleanFlag NEW_HEADER = new BooleanFlag(505, false);
+    public static final BooleanFlag NEW_HEADER = new BooleanFlag(505, false, true);
     public static final ResourceBooleanFlag FULL_SCREEN_USER_SWITCHER =
             new ResourceBooleanFlag(506, R.bool.config_enableFullscreenUserSwitcher);
 
@@ -142,6 +145,9 @@
     public static final ResourceBooleanFlag STATUS_BAR_USER_SWITCHER =
             new ResourceBooleanFlag(602, R.bool.flag_user_switcher_chip);
 
+    public static final BooleanFlag STATUS_BAR_LETTERBOX_APPEARANCE =
+            new BooleanFlag(603, false);
+
     /***************************************/
     // 700 - dialer/calls
     public static final BooleanFlag ONGOING_CALL_STATUS_BAR_CHIP =
diff --git a/packages/SystemUI/src/com/android/systemui/shade/CombinedShadeHeadersConstraintManager.kt b/packages/SystemUI/src/com/android/systemui/shade/CombinedShadeHeadersConstraintManager.kt
new file mode 100644
index 0000000..e360ec2
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/shade/CombinedShadeHeadersConstraintManager.kt
@@ -0,0 +1,79 @@
+/*
+ * 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 androidx.constraintlayout.widget.ConstraintSet
+
+typealias ConstraintChange = ConstraintSet.() -> Unit
+
+operator fun ConstraintChange?.plus(other: ConstraintChange?): ConstraintChange? {
+    // Prevent wrapping
+    if (this == null) return other
+    if (other == null) return this
+    else return {
+        this@plus()
+        other()
+    }
+}
+
+/**
+ * Contains all changes that need to be performed to the different [ConstraintSet] in
+ * [LargeScreenShadeHeaderController].
+ */
+data class ConstraintsChanges(
+    val qqsConstraintsChanges: ConstraintChange? = null,
+    val qsConstraintsChanges: ConstraintChange? = null,
+    val largeScreenConstraintsChanges: ConstraintChange? = null
+) {
+    operator fun plus(other: ConstraintsChanges) = ConstraintsChanges(
+        qqsConstraintsChanges + other.qqsConstraintsChanges,
+        qsConstraintsChanges + other.qsConstraintsChanges,
+        largeScreenConstraintsChanges + other.largeScreenConstraintsChanges
+    )
+}
+
+/**
+ * Determines [ConstraintChanges] for [LargeScreenShadeHeaderController] based on configurations.
+ *
+ * Given that the number of different scenarios is not that large, having specific methods instead
+ * of a full map between state and [ConstraintSet] was preferred.
+ */
+interface CombinedShadeHeadersConstraintManager {
+    /**
+     * Changes for when the visibility of the privacy chip changes
+     */
+    fun privacyChipVisibilityConstraints(visible: Boolean): ConstraintsChanges
+
+    /**
+     * Changes for situations with no top center cutout (there may be a corner cutout)
+     */
+    fun emptyCutoutConstraints(): ConstraintsChanges
+
+    /**
+     * Changes to incorporate side insets due to rounded corners/corner cutouts
+     */
+    fun edgesGuidelinesConstraints(
+        cutoutStart: Int,
+        paddingStart: Int,
+        cutoutEnd: Int,
+        paddingEnd: Int
+    ): ConstraintsChanges
+
+    /**
+     * Changes for situations with top center cutout (in this case, there are no corner cutouts).
+     */
+    fun centerCutoutConstraints(rtl: Boolean, offsetFromEdge: Int): ConstraintsChanges
+}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/CombinedShadeHeadersConstraintManagerImpl.kt b/packages/SystemUI/src/com/android/systemui/shade/CombinedShadeHeadersConstraintManagerImpl.kt
new file mode 100644
index 0000000..4063af3
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/shade/CombinedShadeHeadersConstraintManagerImpl.kt
@@ -0,0 +1,109 @@
+/*
+ * 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 android.view.ViewGroup
+import androidx.constraintlayout.widget.ConstraintSet
+import com.android.systemui.R
+import com.android.systemui.statusbar.phone.dagger.CentralSurfacesComponent
+
+/**
+ * Standard implementation of [CombinedShadeHeadersConstraintManager].
+ */
+@CentralSurfacesComponent.CentralSurfacesScope
+object CombinedShadeHeadersConstraintManagerImpl : CombinedShadeHeadersConstraintManager {
+
+    override fun privacyChipVisibilityConstraints(visible: Boolean): ConstraintsChanges {
+        val constraintAlpha = if (visible) 0f else 1f
+        return ConstraintsChanges(
+            qqsConstraintsChanges = {
+                setAlpha(R.id.statusIcons, constraintAlpha)
+                setAlpha(R.id.batteryRemainingIcon, constraintAlpha)
+            }
+        )
+    }
+
+    override fun emptyCutoutConstraints(): ConstraintsChanges {
+        return ConstraintsChanges(
+            qqsConstraintsChanges = {
+                connect(R.id.date, ConstraintSet.END, R.id.barrier, ConstraintSet.START)
+                createBarrier(
+                    R.id.barrier,
+                    ConstraintSet.START,
+                    0,
+                    R.id.statusIcons,
+                    R.id.privacy_container
+                )
+                connect(R.id.statusIcons, ConstraintSet.START, R.id.date, ConstraintSet.END)
+                connect(R.id.privacy_container, ConstraintSet.START, R.id.date, ConstraintSet.END)
+                constrainWidth(R.id.statusIcons, ViewGroup.LayoutParams.WRAP_CONTENT)
+            }
+        )
+    }
+
+    override fun edgesGuidelinesConstraints(
+        cutoutStart: Int,
+        paddingStart: Int,
+        cutoutEnd: Int,
+        paddingEnd: Int
+    ): ConstraintsChanges {
+        val change: ConstraintChange = {
+            setGuidelineBegin(R.id.begin_guide, Math.max(cutoutStart - paddingStart, 0))
+            setGuidelineEnd(R.id.end_guide, Math.max(cutoutEnd - paddingEnd, 0))
+        }
+        return ConstraintsChanges(
+            qqsConstraintsChanges = change,
+            qsConstraintsChanges = change
+        )
+    }
+
+    override fun centerCutoutConstraints(rtl: Boolean, offsetFromEdge: Int): ConstraintsChanges {
+        val centerStart = if (!rtl) R.id.center_left else R.id.center_right
+        val centerEnd = if (!rtl) R.id.center_right else R.id.center_left
+        // Use guidelines to block the center cutout area.
+        return ConstraintsChanges(
+            qqsConstraintsChanges = {
+                setGuidelineBegin(centerStart, offsetFromEdge)
+                setGuidelineEnd(centerEnd, offsetFromEdge)
+                connect(R.id.date, ConstraintSet.END, centerStart, ConstraintSet.START)
+                connect(
+                    R.id.statusIcons,
+                    ConstraintSet.START,
+                    centerEnd,
+                    ConstraintSet.END
+                )
+                connect(
+                    R.id.privacy_container,
+                    ConstraintSet.START,
+                    centerEnd,
+                    ConstraintSet.END
+                )
+                constrainWidth(R.id.statusIcons, 0)
+            },
+            qsConstraintsChanges = {
+                setGuidelineBegin(centerStart, offsetFromEdge)
+                setGuidelineEnd(centerEnd, offsetFromEdge)
+                connect(
+                    R.id.privacy_container,
+                    ConstraintSet.START,
+                    centerEnd,
+                    ConstraintSet.END
+                )
+            }
+        )
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/LargeScreenShadeHeaderController.kt b/packages/SystemUI/src/com/android/systemui/shade/LargeScreenShadeHeaderController.kt
new file mode 100644
index 0000000..5793105
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/shade/LargeScreenShadeHeaderController.kt
@@ -0,0 +1,510 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.shade
+
+import android.annotation.IdRes
+import android.app.StatusBarManager
+import android.content.res.Configuration
+import android.os.Trace
+import android.os.Trace.TRACE_TAG_APP
+import android.util.Pair
+import android.view.View
+import android.view.WindowInsets
+import android.widget.TextView
+import androidx.annotation.VisibleForTesting
+import androidx.constraintlayout.motion.widget.MotionLayout
+import com.android.settingslib.Utils
+import com.android.systemui.Dumpable
+import com.android.systemui.R
+import com.android.systemui.animation.ShadeInterpolation
+import com.android.systemui.battery.BatteryMeterView
+import com.android.systemui.battery.BatteryMeterViewController
+import com.android.systemui.dump.DumpManager
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags
+import com.android.systemui.qs.ChipVisibilityListener
+import com.android.systemui.qs.HeaderPrivacyIconsController
+import com.android.systemui.qs.carrier.QSCarrierGroup
+import com.android.systemui.qs.carrier.QSCarrierGroupController
+import com.android.systemui.shade.LargeScreenShadeHeaderController.Companion.HEADER_TRANSITION_ID
+import com.android.systemui.shade.LargeScreenShadeHeaderController.Companion.LARGE_SCREEN_HEADER_CONSTRAINT
+import com.android.systemui.shade.LargeScreenShadeHeaderController.Companion.LARGE_SCREEN_HEADER_TRANSITION_ID
+import com.android.systemui.shade.LargeScreenShadeHeaderController.Companion.QQS_HEADER_CONSTRAINT
+import com.android.systemui.shade.LargeScreenShadeHeaderController.Companion.QS_HEADER_CONSTRAINT
+import com.android.systemui.statusbar.phone.StatusBarContentInsetsProvider
+import com.android.systemui.statusbar.phone.StatusBarIconController
+import com.android.systemui.statusbar.phone.StatusIconContainer
+import com.android.systemui.statusbar.phone.dagger.CentralSurfacesComponent.CentralSurfacesScope
+import com.android.systemui.statusbar.phone.dagger.StatusBarViewModule.LARGE_SCREEN_BATTERY_CONTROLLER
+import com.android.systemui.statusbar.phone.dagger.StatusBarViewModule.LARGE_SCREEN_SHADE_HEADER
+import com.android.systemui.statusbar.policy.ConfigurationController
+import com.android.systemui.statusbar.policy.VariableDateView
+import com.android.systemui.statusbar.policy.VariableDateViewController
+import com.android.systemui.util.ViewController
+import java.io.PrintWriter
+import javax.inject.Inject
+import javax.inject.Named
+
+/**
+ * Controller for QS header on Large Screen width (large screen + landscape).
+ *
+ * Additionally, this serves as the staging ground for the combined QS headers. A single
+ * [MotionLayout] that changes constraints depending on the configuration and can animate the
+ * expansion of the headers in small screen portrait.
+ *
+ * [header] will be a [MotionLayout] if [Flags.COMBINED_QS_HEADERS] is enabled. In this case, the
+ * [MotionLayout] has 2 transitions:
+ * * [HEADER_TRANSITION_ID]: [QQS_HEADER_CONSTRAINT] <-> [QS_HEADER_CONSTRAINT] for portrait
+ *   handheld device configuration.
+ * * [LARGE_SCREEN_HEADER_TRANSITION_ID]: [LARGE_SCREEN_HEADER_CONSTRAINT] (to itself) for all
+ *   other configurations
+ */
+@CentralSurfacesScope
+class LargeScreenShadeHeaderController @Inject constructor(
+    @Named(LARGE_SCREEN_SHADE_HEADER) private val header: View,
+    private val statusBarIconController: StatusBarIconController,
+    private val privacyIconsController: HeaderPrivacyIconsController,
+    private val insetsProvider: StatusBarContentInsetsProvider,
+    private val configurationController: ConfigurationController,
+    private val variableDateViewControllerFactory: VariableDateViewController.Factory,
+    @Named(LARGE_SCREEN_BATTERY_CONTROLLER)
+    private val batteryMeterViewController: BatteryMeterViewController,
+    private val dumpManager: DumpManager,
+    private val featureFlags: FeatureFlags,
+    private val qsCarrierGroupControllerBuilder: QSCarrierGroupController.Builder,
+    private val combinedShadeHeadersConstraintManager: CombinedShadeHeadersConstraintManager
+) : ViewController<View>(header), Dumpable {
+
+    companion object {
+        /** IDs for transitions and constraints for the [MotionLayout]. These are only used when
+         * [Flags.COMBINED_QS_HEADERS] is enabled.
+         */
+        @VisibleForTesting
+        internal val HEADER_TRANSITION_ID = R.id.header_transition
+        @VisibleForTesting
+        internal val LARGE_SCREEN_HEADER_TRANSITION_ID = R.id.large_screen_header_transition
+        @VisibleForTesting
+        internal val QQS_HEADER_CONSTRAINT = R.id.qqs_header_constraint
+        @VisibleForTesting
+        internal val QS_HEADER_CONSTRAINT = R.id.qs_header_constraint
+        @VisibleForTesting
+        internal val LARGE_SCREEN_HEADER_CONSTRAINT = R.id.large_screen_header_constraint
+
+        private fun Int.stateToString() = when (this) {
+            QQS_HEADER_CONSTRAINT -> "QQS Header"
+            QS_HEADER_CONSTRAINT -> "QS Header"
+            LARGE_SCREEN_HEADER_CONSTRAINT -> "Large Screen Header"
+            else -> "Unknown state"
+        }
+    }
+
+    init {
+        loadConstraints()
+    }
+
+    private val combinedHeaders = featureFlags.isEnabled(Flags.COMBINED_QS_HEADERS)
+
+    private lateinit var iconManager: StatusBarIconController.TintedIconManager
+    private lateinit var carrierIconSlots: List<String>
+    private lateinit var qsCarrierGroupController: QSCarrierGroupController
+
+    private val batteryIcon: BatteryMeterView = header.findViewById(R.id.batteryRemainingIcon)
+    private val clock: TextView = header.findViewById(R.id.clock)
+    private val date: TextView = header.findViewById(R.id.date)
+    private val iconContainer: StatusIconContainer = header.findViewById(R.id.statusIcons)
+    private val qsCarrierGroup: QSCarrierGroup = header.findViewById(R.id.carrier_group)
+
+    private var cutoutLeft = 0
+    private var cutoutRight = 0
+    private var roundedCorners = 0
+    private var lastInsets: WindowInsets? = null
+
+    private var qsDisabled = false
+    private var visible = false
+        set(value) {
+            if (field == value) {
+                return
+            }
+            field = value
+            updateListeners()
+        }
+
+    /**
+     * Whether the QQS/QS part of the shade is visible. This is particularly important in
+     * Lockscreen, as the shade is visible but QS is not.
+     */
+    var qsVisible = false
+        set(value) {
+            if (field == value) {
+                return
+            }
+            field = value
+            onShadeExpandedChanged()
+        }
+
+    /**
+     * Whether we are in a configuration with large screen width. In this case, the header is a
+     * single line.
+     */
+    var largeScreenActive = false
+        set(value) {
+            if (field == value) {
+                return
+            }
+            field = value
+            onHeaderStateChanged()
+        }
+
+    /**
+     * Expansion fraction of the QQS/QS shade. This is not the expansion between QQS <-> QS.
+     */
+    var shadeExpandedFraction = -1f
+        set(value) {
+            if (visible && field != value) {
+                header.alpha = ShadeInterpolation.getContentAlpha(value)
+                field = value
+            }
+        }
+
+    /**
+     * Expansion fraction of the QQS <-> QS animation.
+     */
+    var qsExpandedFraction = -1f
+        set(value) {
+            if (visible && field != value) {
+                field = value
+                updatePosition()
+            }
+        }
+
+    /**
+     * Current scroll of QS.
+     */
+    var qsScrollY = 0
+        set(value) {
+            if (field != value) {
+                field = value
+                updateScrollY()
+            }
+        }
+
+    private val insetListener = View.OnApplyWindowInsetsListener { view, insets ->
+        updateConstraintsForInsets(view as MotionLayout, insets)
+        lastInsets = WindowInsets(insets)
+
+        view.onApplyWindowInsets(insets)
+    }
+
+    private val chipVisibilityListener: ChipVisibilityListener = object : ChipVisibilityListener {
+        override fun onChipVisibilityRefreshed(visible: Boolean) {
+            if (header is MotionLayout) {
+                // If the privacy chip is visible, we hide the status icons and battery remaining
+                // icon, only in QQS.
+                val update = combinedShadeHeadersConstraintManager
+                    .privacyChipVisibilityConstraints(visible)
+                header.updateAllConstraints(update)
+            }
+        }
+    }
+
+    private val configurationControllerListener =
+        object : ConfigurationController.ConfigurationListener {
+        override fun onConfigChanged(newConfig: Configuration?) {
+            if (header !is MotionLayout) {
+                val left = header.resources.getDimensionPixelSize(
+                    R.dimen.large_screen_shade_header_left_padding
+                )
+                header.setPadding(
+                    left,
+                    header.paddingTop,
+                    header.paddingRight,
+                    header.paddingBottom
+                )
+            }
+        }
+
+        override fun onDensityOrFontScaleChanged() {
+            clock.setTextAppearance(R.style.TextAppearance_QS_Status)
+            date.setTextAppearance(R.style.TextAppearance_QS_Status)
+            qsCarrierGroup.updateTextAppearance(R.style.TextAppearance_QS_Status_Carriers)
+            if (header is MotionLayout) {
+                loadConstraints()
+                lastInsets?.let { updateConstraintsForInsets(header, it) }
+            }
+            updateResources()
+        }
+    }
+
+    override fun onInit() {
+        if (header is MotionLayout) {
+            variableDateViewControllerFactory.create(date as VariableDateView).init()
+        }
+        batteryMeterViewController.init()
+
+        // battery settings same as in QS icons
+        batteryMeterViewController.ignoreTunerUpdates()
+        batteryIcon.setPercentShowMode(BatteryMeterView.MODE_ESTIMATE)
+
+        iconManager = StatusBarIconController.TintedIconManager(iconContainer, featureFlags)
+        iconManager.setTint(
+            Utils.getColorAttrDefaultColor(header.context, android.R.attr.textColorPrimary)
+        )
+
+        carrierIconSlots = if (featureFlags.isEnabled(Flags.COMBINED_STATUS_BAR_SIGNAL_ICONS)) {
+            listOf(
+                header.context.getString(com.android.internal.R.string.status_bar_no_calling),
+                header.context.getString(com.android.internal.R.string.status_bar_call_strength)
+            )
+        } else {
+            listOf(header.context.getString(com.android.internal.R.string.status_bar_mobile))
+        }
+        qsCarrierGroupController = qsCarrierGroupControllerBuilder
+            .setQSCarrierGroup(qsCarrierGroup)
+            .build()
+    }
+
+    override fun onViewAttached() {
+        privacyIconsController.chipVisibilityListener = chipVisibilityListener
+        if (header is MotionLayout) {
+            header.setOnApplyWindowInsetsListener(insetListener)
+            clock.addOnLayoutChangeListener { v, _, _, _, _, _, _, _, _ ->
+                val newPivot = if (v.isLayoutRtl) v.width.toFloat() else 0f
+                v.pivotX = newPivot
+            }
+        }
+
+        dumpManager.registerDumpable(this)
+        configurationController.addCallback(configurationControllerListener)
+
+        updateVisibility()
+        updateTransition()
+    }
+
+    override fun onViewDetached() {
+        privacyIconsController.chipVisibilityListener = null
+        dumpManager.unregisterDumpable(this::class.java.simpleName)
+        configurationController.removeCallback(configurationControllerListener)
+    }
+
+    fun disable(state1: Int, state2: Int, animate: Boolean) {
+        val disabled = state2 and StatusBarManager.DISABLE2_QUICK_SETTINGS != 0
+        if (disabled == qsDisabled) return
+        qsDisabled = disabled
+        updateVisibility()
+    }
+
+    private fun loadConstraints() {
+        if (header is MotionLayout) {
+            // Use resources.getXml instead of passing the resource id due to bug b/205018300
+            header.getConstraintSet(QQS_HEADER_CONSTRAINT)
+                .load(context, resources.getXml(R.xml.qqs_header))
+            val qsConstraints = if (featureFlags.isEnabled(Flags.NEW_HEADER)) {
+                R.xml.qs_header_new
+            } else {
+                R.xml.qs_header
+            }
+            header.getConstraintSet(QS_HEADER_CONSTRAINT)
+                .load(context, resources.getXml(qsConstraints))
+            header.getConstraintSet(LARGE_SCREEN_HEADER_CONSTRAINT)
+                .load(context, resources.getXml(R.xml.large_screen_shade_header))
+        }
+    }
+
+    private fun updateConstraintsForInsets(view: MotionLayout, insets: WindowInsets) {
+        val cutout = insets.displayCutout
+
+        val sbInsets: Pair<Int, Int> = insetsProvider.getStatusBarContentInsetsForCurrentRotation()
+        cutoutLeft = sbInsets.first
+        cutoutRight = sbInsets.second
+        val hasCornerCutout: Boolean = insetsProvider.currentRotationHasCornerCutout()
+        updateQQSPaddings()
+        // Set these guides as the left/right limits for content that lives in the top row, using
+        // cutoutLeft and cutoutRight
+        var changes = combinedShadeHeadersConstraintManager
+            .edgesGuidelinesConstraints(
+                if (view.isLayoutRtl) cutoutRight else cutoutLeft,
+                header.paddingStart,
+                if (view.isLayoutRtl) cutoutLeft else cutoutRight,
+                header.paddingEnd
+            )
+
+        if (cutout != null) {
+            val topCutout = cutout.boundingRectTop
+            if (topCutout.isEmpty || hasCornerCutout) {
+                changes += combinedShadeHeadersConstraintManager.emptyCutoutConstraints()
+            } else {
+                changes += combinedShadeHeadersConstraintManager.centerCutoutConstraints(
+                    view.isLayoutRtl,
+                    (view.width - view.paddingLeft - view.paddingRight - topCutout.width()) / 2
+                )
+            }
+        } else {
+           changes += combinedShadeHeadersConstraintManager.emptyCutoutConstraints()
+        }
+
+        view.updateAllConstraints(changes)
+    }
+
+    private fun updateScrollY() {
+        if (!largeScreenActive && combinedHeaders) {
+            header.scrollY = qsScrollY
+        }
+    }
+
+    private fun onShadeExpandedChanged() {
+        if (qsVisible) {
+            privacyIconsController.startListening()
+        } else {
+            privacyIconsController.stopListening()
+        }
+        updateVisibility()
+        updatePosition()
+    }
+
+    private fun onHeaderStateChanged() {
+        if (largeScreenActive || combinedHeaders) {
+            privacyIconsController.onParentVisible()
+        } else {
+            privacyIconsController.onParentInvisible()
+        }
+        updateVisibility()
+        updateTransition()
+    }
+
+    /**
+     * If not using [combinedHeaders] this should only be visible on large screen. Else, it should
+     * be visible any time the QQS/QS shade is open.
+     */
+    private fun updateVisibility() {
+        val visibility = if (!largeScreenActive && !combinedHeaders || qsDisabled) {
+            View.GONE
+        } else if (qsVisible) {
+            View.VISIBLE
+        } else {
+            View.INVISIBLE
+        }
+        if (header.visibility != visibility) {
+            header.visibility = visibility
+            visible = visibility == View.VISIBLE
+        }
+    }
+
+    private fun updateTransition() {
+        if (!combinedHeaders) {
+            return
+        }
+        header as MotionLayout
+        if (largeScreenActive) {
+            header.setTransition(LARGE_SCREEN_HEADER_TRANSITION_ID)
+            header.getConstraintSet(LARGE_SCREEN_HEADER_CONSTRAINT).applyTo(header)
+        } else {
+            header.setTransition(HEADER_TRANSITION_ID)
+            header.transitionToStart()
+            updatePosition()
+            updateScrollY()
+        }
+    }
+
+    private fun updatePosition() {
+        if (header is MotionLayout && !largeScreenActive && visible) {
+            Trace.instantForTrack(
+                TRACE_TAG_APP,
+                "LargeScreenHeaderController - updatePosition",
+                "position: $qsExpandedFraction"
+            )
+            header.progress = qsExpandedFraction
+        }
+    }
+
+    private fun updateListeners() {
+        qsCarrierGroupController.setListening(visible)
+        if (visible) {
+            updateSingleCarrier(qsCarrierGroupController.isSingleCarrier)
+            qsCarrierGroupController.setOnSingleCarrierChangedListener { updateSingleCarrier(it) }
+            statusBarIconController.addIconGroup(iconManager)
+        } else {
+            qsCarrierGroupController.setOnSingleCarrierChangedListener(null)
+            statusBarIconController.removeIconGroup(iconManager)
+        }
+    }
+
+    private fun updateSingleCarrier(singleCarrier: Boolean) {
+        if (singleCarrier) {
+            iconContainer.removeIgnoredSlots(carrierIconSlots)
+        } else {
+            iconContainer.addIgnoredSlots(carrierIconSlots)
+        }
+    }
+
+    private fun updateResources() {
+        roundedCorners = resources.getDimensionPixelSize(R.dimen.rounded_corner_content_padding)
+        val padding = resources.getDimensionPixelSize(R.dimen.qs_panel_padding)
+        header.setPadding(padding, header.paddingTop, padding, header.paddingBottom)
+        updateQQSPaddings()
+    }
+
+    private fun updateQQSPaddings() {
+        if (header is MotionLayout) {
+            val clockPaddingStart = resources
+                .getDimensionPixelSize(R.dimen.status_bar_left_clock_starting_padding)
+            val clockPaddingEnd = resources
+                .getDimensionPixelSize(R.dimen.status_bar_left_clock_end_padding)
+            clock.setPaddingRelative(
+                clockPaddingStart,
+                clock.paddingTop,
+                clockPaddingEnd,
+                clock.paddingBottom
+            )
+        }
+    }
+
+    override fun dump(pw: PrintWriter, args: Array<out String>) {
+        pw.println("visible: $visible")
+        pw.println("shadeExpanded: $qsVisible")
+        pw.println("shadeExpandedFraction: $shadeExpandedFraction")
+        pw.println("active: $largeScreenActive")
+        pw.println("qsExpandedFraction: $qsExpandedFraction")
+        pw.println("qsScrollY: $qsScrollY")
+        if (combinedHeaders) {
+            header as MotionLayout
+            pw.println("currentState: ${header.currentState.stateToString()}")
+        }
+    }
+
+    private fun MotionLayout.updateConstraints(@IdRes state: Int, update: ConstraintChange) {
+        val constraints = getConstraintSet(state)
+        constraints.update()
+        updateState(state, constraints)
+    }
+
+    /**
+     * Updates the [ConstraintSet] for the case of combined headers.
+     *
+     * Only non-`null` changes are applied to reduce the number of rebuilding in the [MotionLayout].
+     */
+    private fun MotionLayout.updateAllConstraints(updates: ConstraintsChanges) {
+        if (updates.qqsConstraintsChanges != null) {
+            updateConstraints(QQS_HEADER_CONSTRAINT, updates.qqsConstraintsChanges)
+        }
+        if (updates.qsConstraintsChanges != null) {
+            updateConstraints(QS_HEADER_CONSTRAINT, updates.qsConstraintsChanges)
+        }
+        if (updates.largeScreenConstraintsChanges != null) {
+            updateConstraints(LARGE_SCREEN_HEADER_CONSTRAINT, updates.largeScreenConstraintsChanges)
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelView.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelView.java
index e0997ff..9818af3 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelView.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelView.java
@@ -25,7 +25,6 @@
 import android.util.AttributeSet;
 
 import com.android.systemui.R;
-import com.android.systemui.statusbar.phone.PanelView;
 import com.android.systemui.statusbar.phone.TapAgainView;
 
 public class NotificationPanelView extends PanelView {
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
index bab92ba..99d0fe9 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
@@ -189,12 +189,9 @@
 import com.android.systemui.statusbar.phone.KeyguardClockPositionAlgorithm;
 import com.android.systemui.statusbar.phone.KeyguardStatusBarView;
 import com.android.systemui.statusbar.phone.KeyguardStatusBarViewController;
-import com.android.systemui.statusbar.phone.LargeScreenShadeHeaderController;
 import com.android.systemui.statusbar.phone.LockscreenGestureLogger;
 import com.android.systemui.statusbar.phone.LockscreenGestureLogger.LockscreenUiEvent;
 import com.android.systemui.statusbar.phone.NotificationIconAreaController;
-import com.android.systemui.statusbar.phone.PanelView;
-import com.android.systemui.statusbar.phone.PanelViewController;
 import com.android.systemui.statusbar.phone.PhoneStatusBarView;
 import com.android.systemui.statusbar.phone.ScreenOffAnimationController;
 import com.android.systemui.statusbar.phone.ScrimController;
@@ -1038,6 +1035,7 @@
         }
 
         mTapAgainViewController.init();
+        mLargeScreenShadeHeaderController.init();
         mKeyguardUnfoldTransition.ifPresent(u -> u.setup(mView));
         mNotificationPanelUnfoldAnimationController.ifPresent(controller ->
                 controller.setup(mNotificationContainerParent));
@@ -1143,7 +1141,7 @@
                 SystemBarUtils.getQuickQsOffsetHeight(mView.getContext());
         int topMargin = mUseLargeScreenShadeHeader ? mLargeScreenShadeHeaderHeight :
                 mResources.getDimensionPixelSize(R.dimen.notification_panel_margin_top);
-        mLargeScreenShadeHeaderController.setActive(mUseLargeScreenShadeHeader);
+        mLargeScreenShadeHeaderController.setLargeScreenActive(mUseLargeScreenShadeHeader);
         mAmbientState.setStackTopMargin(topMargin);
         mNotificationsQSContainerController.updateResources();
 
@@ -1716,10 +1714,17 @@
     /**
      * Animate QS closing by flinging it.
      * If QS is expanded, it will collapse into QQS and stop.
+     * If in split shade, it will collapse the whole shade.
      *
      * @param animateAway Do not stop when QS becomes QQS. Fling until QS isn't visible anymore.
      */
     public void animateCloseQs(boolean animateAway) {
+        if (mSplitShadeEnabled) {
+            collapsePanel(
+                    /* animate= */true, /* delayed= */false, /* speedUpFactor= */1.0f);
+            return;
+        }
+
         if (mQsExpansionAnimator != null) {
             if (!mQsAnimatorExpand) {
                 return;
@@ -2417,7 +2422,7 @@
                 : getExpandedFraction();
         mLargeScreenShadeHeaderController.setShadeExpandedFraction(shadeExpandedFraction);
         mLargeScreenShadeHeaderController.setQsExpandedFraction(qsExpansionFraction);
-        mLargeScreenShadeHeaderController.setShadeExpanded(mQsVisible);
+        mLargeScreenShadeHeaderController.setQsVisible(mQsVisible);
     }
 
     private void onStackYChanged(boolean shouldAnimate) {
@@ -3392,19 +3397,11 @@
         return mQsExpanded;
     }
 
-    public boolean isQsDetailShowing() {
-        return mQs.isShowingDetail();
-    }
-
     /** Returns whether the QS customizer is currently active. */
     public boolean isQsCustomizing() {
         return mQs.isCustomizing();
     }
 
-    public void closeQsDetail() {
-        mQs.closeDetail();
-    }
-
     /** Close the QS customizer if it is open. */
     public void closeQsCustomizer() {
         mQs.closeCustomizer();
@@ -3748,6 +3745,8 @@
 
         final float dozeAmount = dozing ? 1 : 0;
         mStatusBarStateController.setAndInstrumentDozeAmount(mView, dozeAmount, animate);
+
+        updateKeyguardStatusViewAlignment(animate);
     }
 
     public void setPulsing(boolean pulsing) {
diff --git a/packages/SystemUI/src/com/android/systemui/shade/OWNERS b/packages/SystemUI/src/com/android/systemui/shade/OWNERS
index 133711e..7dc9dc7 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/OWNERS
+++ b/packages/SystemUI/src/com/android/systemui/shade/OWNERS
@@ -3,6 +3,7 @@
 
 per-file NotificationsQuickSettingsContainer.java = kozynski@google.com, asc@google.com
 per-file NotificationsQSContainerController.kt = kozynski@google.com, asc@google.com
+per-file *ShadeHeader* = kozynski@google.com, asc@google.com
 
 per-file NotificationShadeWindowViewController.java = pixel@google.com, cinek@google.com, juliacr@google.com
 per-file NotificationShadeWindowView.java = pixel@google.com, cinek@google.com, juliacr@google.com
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java b/packages/SystemUI/src/com/android/systemui/shade/PanelView.java
similarity index 91%
rename from packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java
rename to packages/SystemUI/src/com/android/systemui/shade/PanelView.java
index 45dc943..1082967 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/PanelView.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.systemui.statusbar.phone;
+package com.android.systemui.shade;
 
 import android.content.Context;
 import android.content.res.Configuration;
@@ -22,6 +22,10 @@
 import android.view.MotionEvent;
 import android.widget.FrameLayout;
 
+import com.android.systemui.statusbar.phone.CentralSurfaces;
+import com.android.systemui.statusbar.phone.HeadsUpManagerPhone;
+import com.android.systemui.statusbar.phone.KeyguardBottomAreaView;
+
 public abstract class PanelView extends FrameLayout {
     public static final boolean DEBUG = false;
     public static final String TAG = PanelView.class.getSimpleName();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/PanelViewController.java
similarity index 98%
rename from packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelViewController.java
rename to packages/SystemUI/src/com/android/systemui/shade/PanelViewController.java
index 3a85a3e..229acf4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/PanelViewController.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.systemui.statusbar.phone;
+package com.android.systemui.shade;
 
 import static android.view.View.INVISIBLE;
 import static android.view.View.VISIBLE;
@@ -60,7 +60,14 @@
 import com.android.systemui.statusbar.SysuiStatusBarStateController;
 import com.android.systemui.statusbar.VibratorHelper;
 import com.android.systemui.statusbar.notification.stack.AmbientState;
+import com.android.systemui.statusbar.phone.BounceInterpolator;
+import com.android.systemui.statusbar.phone.CentralSurfaces;
+import com.android.systemui.statusbar.phone.HeadsUpManagerPhone;
+import com.android.systemui.statusbar.phone.KeyguardBottomAreaView;
+import com.android.systemui.statusbar.phone.LockscreenGestureLogger;
 import com.android.systemui.statusbar.phone.LockscreenGestureLogger.LockscreenUiEvent;
+import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
+import com.android.systemui.statusbar.phone.StatusBarTouchableRegionManager;
 import com.android.systemui.statusbar.phone.panelstate.PanelExpansionStateManager;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
 import com.android.systemui.util.time.SystemClock;
@@ -388,7 +395,7 @@
         return Math.abs(yDiff) >= Math.abs(xDiff);
     }
 
-    protected void startExpandMotion(float newX, float newY, boolean startTracking,
+    public void startExpandMotion(float newX, float newY, boolean startTracking,
             float expandedHeight) {
         if (!mHandlingPointerUp && !mStatusBarStateController.isDozing()) {
             beginJankMonitoring(CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotifPipelineFlags.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotifPipelineFlags.kt
index c4947ca..0c6a91f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotifPipelineFlags.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotifPipelineFlags.kt
@@ -59,4 +59,7 @@
 
     fun removeUnrankedNotifs(): Boolean =
         featureFlags.isEnabled(Flags.REMOVE_UNRANKED_NOTIFICATIONS)
+
+    fun fullScreenIntentRequiresKeyguard(): Boolean =
+        featureFlags.isEnabled(Flags.FSI_REQUIRES_KEYGUARD)
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImpl.java
index 535dc6e..a72b381 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImpl.java
@@ -41,6 +41,7 @@
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.policy.BatteryController;
 import com.android.systemui.statusbar.policy.HeadsUpManager;
+import com.android.systemui.statusbar.policy.KeyguardStateController;
 
 import java.util.ArrayList;
 import java.util.List;
@@ -58,6 +59,7 @@
 
     private final List<NotificationInterruptSuppressor> mSuppressors = new ArrayList<>();
     private final StatusBarStateController mStatusBarStateController;
+    private final KeyguardStateController mKeyguardStateController;
     private final NotificationFilter mNotificationFilter;
     private final ContentResolver mContentResolver;
     private final PowerManager mPowerManager;
@@ -82,6 +84,7 @@
             NotificationFilter notificationFilter,
             BatteryController batteryController,
             StatusBarStateController statusBarStateController,
+            KeyguardStateController keyguardStateController,
             HeadsUpManager headsUpManager,
             NotificationInterruptLogger logger,
             @Main Handler mainHandler,
@@ -94,6 +97,7 @@
         mAmbientDisplayConfiguration = ambientDisplayConfiguration;
         mNotificationFilter = notificationFilter;
         mStatusBarStateController = statusBarStateController;
+        mKeyguardStateController = keyguardStateController;
         mHeadsUpManager = headsUpManager;
         mLogger = logger;
         mFlags = flags;
@@ -228,6 +232,28 @@
             return false;
         }
 
+        // Check whether FSI requires the keyguard to be showing.
+        if (mFlags.fullScreenIntentRequiresKeyguard()) {
+
+            // If notification won't HUN and keyguard is showing, launch the FSI.
+            if (mKeyguardStateController.isShowing()) {
+                if (mKeyguardStateController.isOccluded()) {
+                    mLogger.logFullscreen(entry, "Expected not to HUN while keyguard occluded");
+                } else {
+                    // Likely LOCKED_SHADE, but launch FSI anyway
+                    mLogger.logFullscreen(entry, "Keyguard is showing and not occluded");
+                }
+                return true;
+            }
+
+            // Detect the case determined by b/231322873 to launch FSI while device is in use,
+            // as blocked by the correct implementation, and report the event.
+            final int uid = entry.getSbn().getUid();
+            android.util.EventLog.writeEvent(0x534e4554, "231322873", uid, "no hun or keyguard");
+            mLogger.logNoFullscreenWarning(entry, "Expected not to HUN while not on keyguard");
+            return false;
+        }
+
         // If the notification won't HUN for some other reason (DND/snooze/etc), launch FSI.
         mLogger.logFullscreen(entry, "Expected not to HUN");
         return true;
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 2d6d846..98404eb 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
@@ -3325,11 +3325,7 @@
             return true;
         }
         if (mNotificationPanelViewController.isQsExpanded()) {
-            if (mNotificationPanelViewController.isQsDetailShowing()) {
-                mNotificationPanelViewController.closeQsDetail();
-            } else {
                 mNotificationPanelViewController.animateCloseQs(false /* animateAway */);
-            }
             return true;
         }
         if (mNotificationPanelViewController.closeUserSwitcherIfOpen()) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LargeScreenShadeHeaderController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LargeScreenShadeHeaderController.kt
deleted file mode 100644
index 84c8700..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LargeScreenShadeHeaderController.kt
+++ /dev/null
@@ -1,311 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.statusbar.phone
-
-import android.app.StatusBarManager
-import android.content.res.Configuration
-import android.view.View
-import android.widget.TextView
-import androidx.constraintlayout.motion.widget.MotionLayout
-import com.android.settingslib.Utils
-import com.android.systemui.Dumpable
-import com.android.systemui.FontSizeUtils
-import com.android.systemui.R
-import com.android.systemui.animation.ShadeInterpolation
-import com.android.systemui.battery.BatteryMeterView
-import com.android.systemui.battery.BatteryMeterViewController
-import com.android.systemui.dump.DumpManager
-import com.android.systemui.flags.FeatureFlags
-import com.android.systemui.flags.Flags
-import com.android.systemui.qs.ChipVisibilityListener
-import com.android.systemui.qs.HeaderPrivacyIconsController
-import com.android.systemui.qs.carrier.QSCarrierGroup
-import com.android.systemui.qs.carrier.QSCarrierGroupController
-import com.android.systemui.statusbar.phone.dagger.CentralSurfacesComponent.CentralSurfacesScope
-import com.android.systemui.statusbar.phone.dagger.StatusBarViewModule.LARGE_SCREEN_BATTERY_CONTROLLER
-import com.android.systemui.statusbar.phone.dagger.StatusBarViewModule.LARGE_SCREEN_SHADE_HEADER
-import com.android.systemui.statusbar.policy.ConfigurationController
-import java.io.PrintWriter
-import javax.inject.Inject
-import javax.inject.Named
-
-@CentralSurfacesScope
-class LargeScreenShadeHeaderController @Inject constructor(
-    @Named(LARGE_SCREEN_SHADE_HEADER) private val header: View,
-    private val statusBarIconController: StatusBarIconController,
-    private val privacyIconsController: HeaderPrivacyIconsController,
-    private val configurationController: ConfigurationController,
-    qsCarrierGroupControllerBuilder: QSCarrierGroupController.Builder,
-    featureFlags: FeatureFlags,
-    @Named(LARGE_SCREEN_BATTERY_CONTROLLER) batteryMeterViewController: BatteryMeterViewController,
-    dumpManager: DumpManager
-) : Dumpable {
-
-    companion object {
-        private val HEADER_TRANSITION_ID = R.id.header_transition
-        private val LARGE_SCREEN_HEADER_TRANSITION_ID = R.id.large_screen_header_transition
-        private val QQS_HEADER_CONSTRAINT = R.id.qqs_header_constraint
-        private val QS_HEADER_CONSTRAINT = R.id.qs_header_constraint
-        private val LARGE_SCREEN_HEADER_CONSTRAINT = R.id.large_screen_header_constraint
-
-        private fun Int.stateToString() = when (this) {
-            QQS_HEADER_CONSTRAINT -> "QQS Header"
-            QS_HEADER_CONSTRAINT -> "QS Header"
-            LARGE_SCREEN_HEADER_CONSTRAINT -> "Large Screen Header"
-            else -> "Unknown state"
-        }
-    }
-
-    private val combinedHeaders = featureFlags.isEnabled(Flags.COMBINED_QS_HEADERS)
-    private val iconManager: StatusBarIconController.TintedIconManager
-    private val iconContainer: StatusIconContainer
-    private val carrierIconSlots: List<String>
-    private val qsCarrierGroupController: QSCarrierGroupController
-    private val clock: TextView = header.findViewById(R.id.clock)
-    private val date: TextView = header.findViewById(R.id.date)
-    private val qsCarrierGroup: QSCarrierGroup = header.findViewById(R.id.carrier_group)
-
-    private var qsDisabled = false
-
-    private var visible = false
-        set(value) {
-            if (field == value) {
-                return
-            }
-            field = value
-            updateListeners()
-        }
-
-    var shadeExpanded = false
-        set(value) {
-            if (field == value) {
-                return
-            }
-            field = value
-            onShadeExpandedChanged()
-        }
-
-    var active = false
-        set(value) {
-            if (field == value) {
-                return
-            }
-            field = value
-            onHeaderStateChanged()
-        }
-
-    var shadeExpandedFraction = -1f
-        set(value) {
-            if (visible && field != value) {
-                header.alpha = ShadeInterpolation.getContentAlpha(value)
-                field = value
-            }
-        }
-
-    var qsExpandedFraction = -1f
-        set(value) {
-            if (visible && field != value) {
-                field = value
-                updateVisibility()
-                updatePosition()
-            }
-        }
-
-    var qsScrollY = 0
-        set(value) {
-            if (field != value) {
-                field = value
-                updateScrollY()
-            }
-        }
-
-    private val chipVisibilityListener: ChipVisibilityListener = object : ChipVisibilityListener {
-        override fun onChipVisibilityRefreshed(visible: Boolean) {
-            if (header is MotionLayout) {
-                val state = header.getConstraintSet(QQS_HEADER_CONSTRAINT).apply {
-                    setAlpha(R.id.statusIcons, if (visible) 0f else 1f)
-                    setAlpha(R.id.batteryRemainingIcon, if (visible) 0f else 1f)
-                }
-                header.updateState(QQS_HEADER_CONSTRAINT, state)
-            }
-        }
-    }
-
-    init {
-        if (header is MotionLayout) {
-            val context = header.context
-            val resources = header.resources
-            header.getConstraintSet(QQS_HEADER_CONSTRAINT)
-                    .load(context, resources.getXml(R.xml.qqs_header))
-            header.getConstraintSet(QS_HEADER_CONSTRAINT)
-                    .load(context, resources.getXml(R.xml.qs_header))
-            header.getConstraintSet(LARGE_SCREEN_HEADER_CONSTRAINT)
-                    .load(context, resources.getXml(R.xml.large_screen_shade_header))
-            privacyIconsController.chipVisibilityListener = chipVisibilityListener
-        }
-
-        bindConfigurationListener()
-
-        batteryMeterViewController.init()
-        val batteryIcon: BatteryMeterView = header.findViewById(R.id.batteryRemainingIcon)
-
-        // battery settings same as in QS icons
-        batteryMeterViewController.ignoreTunerUpdates()
-        batteryIcon.setPercentShowMode(BatteryMeterView.MODE_ESTIMATE)
-
-        iconContainer = header.findViewById(R.id.statusIcons)
-        iconManager = StatusBarIconController.TintedIconManager(iconContainer, featureFlags)
-        iconManager.setTint(Utils.getColorAttrDefaultColor(header.context,
-                android.R.attr.textColorPrimary))
-
-        carrierIconSlots = if (featureFlags.isEnabled(Flags.COMBINED_STATUS_BAR_SIGNAL_ICONS)) {
-            listOf(
-                header.context.getString(com.android.internal.R.string.status_bar_no_calling),
-                header.context.getString(com.android.internal.R.string.status_bar_call_strength)
-            )
-        } else {
-            listOf(header.context.getString(com.android.internal.R.string.status_bar_mobile))
-        }
-        qsCarrierGroupController = qsCarrierGroupControllerBuilder
-                .setQSCarrierGroup(header.findViewById(R.id.carrier_group))
-                .build()
-
-        dumpManager.registerDumpable(this)
-
-        updateVisibility()
-        updateConstraints()
-    }
-
-    fun disable(state1: Int, state2: Int, animate: Boolean) {
-        val disabled = state2 and StatusBarManager.DISABLE2_QUICK_SETTINGS != 0
-        if (disabled == qsDisabled) return
-        qsDisabled = disabled
-        updateVisibility()
-    }
-
-    private fun updateScrollY() {
-        if (!active && combinedHeaders) {
-            header.scrollY = qsScrollY
-        }
-    }
-
-    private fun bindConfigurationListener() {
-        val listener = object : ConfigurationController.ConfigurationListener {
-            override fun onConfigChanged(newConfig: Configuration?) {
-                val left = header.resources.getDimensionPixelSize(
-                    R.dimen.large_screen_shade_header_left_padding)
-                header.setPadding(
-                    left, header.paddingTop, header.paddingRight, header.paddingBottom)
-            }
-            override fun onDensityOrFontScaleChanged() {
-                val qsStatusStyle = R.style.TextAppearance_QS_Status
-                FontSizeUtils.updateFontSizeFromStyle(clock, qsStatusStyle)
-                FontSizeUtils.updateFontSizeFromStyle(date, qsStatusStyle)
-                qsCarrierGroup.updateTextAppearance(qsStatusStyle)
-            }
-        }
-        configurationController.addCallback(listener)
-    }
-
-    private fun onShadeExpandedChanged() {
-        if (shadeExpanded) {
-            privacyIconsController.startListening()
-        } else {
-            privacyIconsController.stopListening()
-        }
-        updateVisibility()
-        updatePosition()
-    }
-
-    private fun onHeaderStateChanged() {
-        if (active || combinedHeaders) {
-            privacyIconsController.onParentVisible()
-        } else {
-            privacyIconsController.onParentInvisible()
-        }
-        updateVisibility()
-        updateConstraints()
-    }
-
-    private fun updateVisibility() {
-        val visibility = if (!active && !combinedHeaders || qsDisabled) {
-            View.GONE
-        } else if (shadeExpanded) {
-            View.VISIBLE
-        } else {
-            View.INVISIBLE
-        }
-        if (header.visibility != visibility) {
-            header.visibility = visibility
-            visible = visibility == View.VISIBLE
-        }
-    }
-
-    private fun updateConstraints() {
-        if (!combinedHeaders) {
-            return
-        }
-        header as MotionLayout
-        if (active) {
-            header.setTransition(LARGE_SCREEN_HEADER_TRANSITION_ID)
-        } else {
-            header.setTransition(HEADER_TRANSITION_ID)
-            header.transitionToStart()
-            updatePosition()
-            updateScrollY()
-        }
-    }
-
-    private fun updatePosition() {
-        if (header is MotionLayout && !active && visible) {
-            header.setProgress(qsExpandedFraction)
-        }
-    }
-
-    private fun updateListeners() {
-        qsCarrierGroupController.setListening(visible)
-        if (visible) {
-            updateSingleCarrier(qsCarrierGroupController.isSingleCarrier)
-            qsCarrierGroupController.setOnSingleCarrierChangedListener { updateSingleCarrier(it) }
-            statusBarIconController.addIconGroup(iconManager)
-        } else {
-            qsCarrierGroupController.setOnSingleCarrierChangedListener(null)
-            statusBarIconController.removeIconGroup(iconManager)
-        }
-    }
-
-    private fun updateSingleCarrier(singleCarrier: Boolean) {
-        if (singleCarrier) {
-            iconContainer.removeIgnoredSlots(carrierIconSlots)
-        } else {
-            iconContainer.addIgnoredSlots(carrierIconSlots)
-        }
-    }
-
-    override fun dump(pw: PrintWriter, args: Array<out String>) {
-        pw.println("visible: $visible")
-        pw.println("shadeExpanded: $shadeExpanded")
-        pw.println("shadeExpandedFraction: $shadeExpandedFraction")
-        pw.println("active: $active")
-        pw.println("qsExpandedFraction: $qsExpandedFraction")
-        pw.println("qsScrollY: $qsScrollY")
-        if (combinedHeaders) {
-            header as MotionLayout
-            pw.println("currentState: ${header.currentState.stateToString()}")
-        }
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarBoundsProvider.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarBoundsProvider.kt
new file mode 100644
index 0000000..f5ba399
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarBoundsProvider.kt
@@ -0,0 +1,92 @@
+/*
+ * 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.phone
+
+import android.graphics.Rect
+import android.view.View
+import com.android.systemui.statusbar.phone.fragment.dagger.StatusBarFragmentComponent
+import com.android.systemui.statusbar.phone.fragment.dagger.StatusBarFragmentModule.END_SIDE_CONTENT
+import com.android.systemui.statusbar.phone.fragment.dagger.StatusBarFragmentModule.START_SIDE_CONTENT
+import com.android.systemui.statusbar.phone.fragment.dagger.StatusBarFragmentScope
+import com.android.systemui.util.boundsOnScreen
+import javax.inject.Inject
+import javax.inject.Named
+
+/** Provides various bounds within the status bar. */
+@StatusBarFragmentScope
+class StatusBarBoundsProvider
+@Inject
+constructor(
+    private val changeListeners: Set<@JvmSuppressWildcards BoundsChangeListener>,
+    @Named(START_SIDE_CONTENT) private val startSideContent: View,
+    @Named(END_SIDE_CONTENT) private val endSideContent: View,
+) : StatusBarFragmentComponent.Startable {
+
+    interface BoundsChangeListener {
+        fun onStatusBarBoundsChanged()
+    }
+
+    private var previousBounds =
+        BoundsPair(start = startSideContent.boundsOnScreen, end = endSideContent.boundsOnScreen)
+
+    private val layoutListener =
+        View.OnLayoutChangeListener { _, _, _, _, _, _, _, _, _ ->
+            val newBounds = BoundsPair(start = visibleStartSideBounds, end = visibleEndSideBounds)
+            if (previousBounds != newBounds) {
+                previousBounds = newBounds
+                changeListeners.forEach { it.onStatusBarBoundsChanged() }
+            }
+        }
+
+    override fun start() {
+        startSideContent.addOnLayoutChangeListener(layoutListener)
+        endSideContent.addOnLayoutChangeListener(layoutListener)
+    }
+
+    override fun stop() {
+        startSideContent.removeOnLayoutChangeListener(layoutListener)
+        endSideContent.removeOnLayoutChangeListener(layoutListener)
+    }
+
+    /**
+     * Returns the bounds of the end side of the status bar that are visible to the user. The end
+     * side is right when in LTR and is left when in RTL.
+     *
+     * Note that even though the layout might be larger, here we only return the bounds for what is
+     * visible to the user.
+     *
+     * The end side of the status bar contains the multi-user switcher and status icons such as
+     * wi-fi, battery, etc
+     */
+    val visibleEndSideBounds: Rect
+        get() = endSideContent.boundsOnScreen
+
+    /**
+     * Returns the bounds of the start side of the status bar that are visible to the user. The
+     * start side is left when in LTR and is right when in RTL.
+     *
+     * Note that even though the layout might be larger, here we only return the bounds for what is
+     * visible to the user.
+     *
+     * The start side of the status bar contains the operator name, clock, on-going call chip, and
+     * notifications.
+     */
+    val visibleStartSideBounds: Rect
+        get() = startSideContent.boundsOnScreen
+}
+
+private data class BoundsPair(val start: Rect, val end: Rect)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarContentInsetsProvider.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarContentInsetsProvider.kt
index f5462bc..c850d4f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarContentInsetsProvider.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarContentInsetsProvider.kt
@@ -123,7 +123,7 @@
         val point = Point()
         context.display.getRealSize(point)
 
-        return topBounds.left <= 0 || topBounds.right >= point.y
+        return topBounds.left <= 0 || topBounds.right >= point.x
     }
 
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java
index 4bbd69b..4c23931 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java
@@ -399,7 +399,8 @@
                 return true;
             }
 
-            if (sbn.getNotification().fullScreenIntent != null) {
+            if (sbn.getNotification().fullScreenIntent != null
+                    && !mNotifPipelineFlags.fullScreenIntentRequiresKeyguard()) {
                 // we don't allow head-up on the lockscreen (unless there's a
                 // "showWhenLocked" activity currently showing)  if
                 // the potential HUN has a fullscreen intent
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/CentralSurfacesComponent.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/CentralSurfacesComponent.java
index 84b2797..64b04e9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/CentralSurfacesComponent.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/CentralSurfacesComponent.java
@@ -22,6 +22,7 @@
 
 import com.android.keyguard.LockIconViewController;
 import com.android.systemui.biometrics.AuthRippleController;
+import com.android.systemui.shade.LargeScreenShadeHeaderController;
 import com.android.systemui.shade.NotificationPanelViewController;
 import com.android.systemui.shade.NotificationShadeWindowView;
 import com.android.systemui.shade.NotificationShadeWindowViewController;
@@ -35,7 +36,6 @@
 import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutListContainerModule;
 import com.android.systemui.statusbar.phone.CentralSurfacesCommandQueueCallbacks;
 import com.android.systemui.statusbar.phone.CentralSurfacesImpl;
-import com.android.systemui.statusbar.phone.LargeScreenShadeHeaderController;
 import com.android.systemui.statusbar.phone.StatusBarHeadsUpChangeListener;
 import com.android.systemui.statusbar.phone.StatusBarNotificationActivityStarterModule;
 import com.android.systemui.statusbar.phone.StatusBarNotificationPresenterModule;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarViewModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarViewModule.java
index 41df8e3..b607391 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarViewModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarViewModule.java
@@ -30,10 +30,13 @@
 import com.android.systemui.biometrics.AuthRippleView;
 import com.android.systemui.broadcast.BroadcastDispatcher;
 import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.dump.DumpManager;
 import com.android.systemui.flags.FeatureFlags;
 import com.android.systemui.flags.Flags;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.privacy.OngoingPrivacyChip;
+import com.android.systemui.shade.CombinedShadeHeadersConstraintManager;
+import com.android.systemui.shade.CombinedShadeHeadersConstraintManagerImpl;
 import com.android.systemui.shade.NotificationPanelView;
 import com.android.systemui.shade.NotificationPanelViewController;
 import com.android.systemui.shade.NotificationShadeWindowView;
@@ -178,6 +181,14 @@
     /** */
     @Provides
     @CentralSurfacesComponent.CentralSurfacesScope
+    public static CombinedShadeHeadersConstraintManager
+            provideCombinedShadeHeadersConstraintManager() {
+        return CombinedShadeHeadersConstraintManagerImpl.INSTANCE;
+    }
+
+    /** */
+    @Provides
+    @CentralSurfacesComponent.CentralSurfacesScope
     public static OngoingPrivacyChip getSplitShadeOngoingPrivacyChip(
             @Named(LARGE_SCREEN_SHADE_HEADER) View header) {
         return header.findViewById(R.id.privacy_chip);
@@ -269,7 +280,8 @@
             CollapsedStatusBarFragmentLogger collapsedStatusBarFragmentLogger,
             OperatorNameViewController.Factory operatorNameViewControllerFactory,
             SecureSettings secureSettings,
-            @Main Executor mainExecutor
+            @Main Executor mainExecutor,
+            DumpManager dumpManager
     ) {
         return new CollapsedStatusBarFragment(statusBarFragmentComponentFactory,
                 ongoingCallController,
@@ -289,7 +301,8 @@
                 collapsedStatusBarFragmentLogger,
                 operatorNameViewControllerFactory,
                 secureSettings,
-                mainExecutor);
+                mainExecutor,
+                dumpManager);
     }
 
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java
index 39bf92a..0848729 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java
@@ -34,6 +34,8 @@
 import android.os.UserHandle;
 import android.provider.Settings;
 import android.telephony.SubscriptionManager;
+import android.util.ArrayMap;
+import android.util.IndentingPrintWriter;
 import android.util.SparseArray;
 import android.view.LayoutInflater;
 import android.view.View;
@@ -43,9 +45,11 @@
 
 import androidx.annotation.VisibleForTesting;
 
+import com.android.systemui.Dumpable;
 import com.android.systemui.R;
 import com.android.systemui.animation.Interpolators;
 import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.dump.DumpManager;
 import com.android.systemui.flags.FeatureFlags;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.shade.NotificationPanelViewController;
@@ -66,6 +70,7 @@
 import com.android.systemui.statusbar.phone.StatusBarIconController.DarkIconManager;
 import com.android.systemui.statusbar.phone.StatusBarLocationPublisher;
 import com.android.systemui.statusbar.phone.fragment.dagger.StatusBarFragmentComponent;
+import com.android.systemui.statusbar.phone.fragment.dagger.StatusBarFragmentComponent.Startable;
 import com.android.systemui.statusbar.phone.ongoingcall.OngoingCallController;
 import com.android.systemui.statusbar.phone.ongoingcall.OngoingCallListener;
 import com.android.systemui.statusbar.phone.panelstate.PanelExpansionStateManager;
@@ -76,9 +81,12 @@
 import com.android.systemui.util.CarrierConfigTracker.DefaultDataSubscriptionChangedListener;
 import com.android.systemui.util.settings.SecureSettings;
 
+import java.io.PrintWriter;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
+import java.util.Map;
+import java.util.Set;
 import java.util.concurrent.Executor;
 
 /**
@@ -89,7 +97,7 @@
 @SuppressLint("ValidFragment")
 public class CollapsedStatusBarFragment extends Fragment implements CommandQueue.Callbacks,
         StatusBarStateController.StateListener,
-        SystemStatusAnimationCallback {
+        SystemStatusAnimationCallback, Dumpable {
 
     public static final String TAG = "CollapsedStatusBarFragment";
     private static final String EXTRA_PANEL_STATE = "panel_state";
@@ -124,8 +132,10 @@
     private final StatusBarHideIconsForBouncerManager mStatusBarHideIconsForBouncerManager;
     private final SecureSettings mSecureSettings;
     private final Executor mMainExecutor;
+    private final DumpManager mDumpManager;
 
     private List<String> mBlockedIcons = new ArrayList<>();
+    private Map<Startable, Startable.State> mStartableStates = new ArrayMap<>();
 
     private SignalCallback mSignalCallback = new SignalCallback() {
         @Override
@@ -185,7 +195,8 @@
             CollapsedStatusBarFragmentLogger collapsedStatusBarFragmentLogger,
             OperatorNameViewController.Factory operatorNameViewControllerFactory,
             SecureSettings secureSettings,
-            @Main Executor mainExecutor
+            @Main Executor mainExecutor,
+            DumpManager dumpManager
     ) {
         mStatusBarFragmentComponentFactory = statusBarFragmentComponentFactory;
         mOngoingCallController = ongoingCallController;
@@ -206,6 +217,7 @@
         mOperatorNameViewControllerFactory = operatorNameViewControllerFactory;
         mSecureSettings = secureSettings;
         mMainExecutor = mainExecutor;
+        mDumpManager = dumpManager;
     }
 
     @Override
@@ -217,8 +229,15 @@
     @Override
     public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
         super.onViewCreated(view, savedInstanceState);
+        mDumpManager.registerDumpable(getClass().getSimpleName(), this);
         mStatusBarFragmentComponent = mStatusBarFragmentComponentFactory.create(this);
         mStatusBarFragmentComponent.init();
+        mStartableStates.clear();
+        for (Startable startable : mStatusBarFragmentComponent.getStartables()) {
+            mStartableStates.put(startable, Startable.State.STARTING);
+            startable.start();
+            mStartableStates.put(startable, Startable.State.STARTED);
+        }
 
         mStatusBar = (PhoneStatusBarView) view;
         View contents = mStatusBar.findViewById(R.id.status_bar_contents);
@@ -321,6 +340,13 @@
         }
         mCarrierConfigTracker.removeCallback(mCarrierConfigCallback);
         mCarrierConfigTracker.removeDataSubscriptionChangedListener(mDefaultDataListener);
+
+        for (Startable startable : mStatusBarFragmentComponent.getStartables()) {
+            mStartableStates.put(startable, Startable.State.STOPPING);
+            startable.stop();
+            mStartableStates.put(startable, Startable.State.STOPPED);
+        }
+        mDumpManager.unregisterDumpable(getClass().getSimpleName());
     }
 
     /** Initializes views related to the notification icon area. */
@@ -670,4 +696,23 @@
                     updateStatusBarLocation(left, right);
                 }
             };
+
+    @Override
+    public void dump(PrintWriter printWriter, String[] args) {
+        IndentingPrintWriter pw = new IndentingPrintWriter(printWriter, /* singleIndent= */"  ");
+        StatusBarFragmentComponent component = mStatusBarFragmentComponent;
+        if (component == null) {
+            pw.println("StatusBarFragmentComponent is null");
+        } else {
+            Set<Startable> startables = component.getStartables();
+            pw.println("Startables: " + startables.size());
+            pw.increaseIndent();
+            for (Startable startable : startables) {
+                Startable.State startableState = mStartableStates.getOrDefault(startable,
+                        Startable.State.NONE);
+                pw.println(startable + ", state: " + startableState);
+            }
+            pw.decreaseIndent();
+        }
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/dagger/StatusBarFragmentComponent.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/dagger/StatusBarFragmentComponent.java
index 6717bc7..d9a5844 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/dagger/StatusBarFragmentComponent.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/dagger/StatusBarFragmentComponent.java
@@ -23,9 +23,12 @@
 import com.android.systemui.statusbar.phone.PhoneStatusBarTransitions;
 import com.android.systemui.statusbar.phone.PhoneStatusBarView;
 import com.android.systemui.statusbar.phone.PhoneStatusBarViewController;
+import com.android.systemui.statusbar.phone.StatusBarBoundsProvider;
 import com.android.systemui.statusbar.phone.StatusBarDemoMode;
 import com.android.systemui.statusbar.phone.fragment.CollapsedStatusBarFragment;
 
+import java.util.Set;
+
 import dagger.BindsInstance;
 import dagger.Subcomponent;
 
@@ -41,7 +44,10 @@
  * should be included here or in {@link StatusBarFragmentModule}.
  */
 
-@Subcomponent(modules = {StatusBarFragmentModule.class})
+@Subcomponent(modules = {
+        StatusBarFragmentModule.class,
+        StatusBarStartablesModule.class
+})
 @StatusBarFragmentScope
 public interface StatusBarFragmentComponent {
     /** Simple factory. */
@@ -52,6 +58,18 @@
     }
 
     /**
+     * Performs initialization logic after {@link StatusBarFragmentComponent} has been constructed.
+     */
+    interface Startable {
+        void start();
+        void stop();
+
+        enum State {
+            NONE, STARTING, STARTED, STOPPING, STOPPED
+        }
+    }
+
+    /**
      * Initialize anything extra for the component. Must be called after the component is created.
      */
     default void init() {
@@ -92,4 +110,10 @@
     /** */
     @StatusBarFragmentScope
     PhoneStatusBarTransitions getPhoneStatusBarTransitions();
+
+    /** */
+    Set<Startable> getStartables();
+
+    /** */
+    StatusBarBoundsProvider getBoundsProvider();
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/dagger/StatusBarFragmentModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/dagger/StatusBarFragmentModule.java
index 7252dfb..41f1f95 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/dagger/StatusBarFragmentModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/dagger/StatusBarFragmentModule.java
@@ -26,6 +26,7 @@
 import com.android.systemui.statusbar.phone.PhoneStatusBarTransitions;
 import com.android.systemui.statusbar.phone.PhoneStatusBarView;
 import com.android.systemui.statusbar.phone.PhoneStatusBarViewController;
+import com.android.systemui.statusbar.phone.StatusBarBoundsProvider;
 import com.android.systemui.statusbar.phone.fragment.CollapsedStatusBarFragment;
 import com.android.systemui.statusbar.phone.userswitcher.StatusBarUserSwitcherContainer;
 import com.android.systemui.statusbar.phone.userswitcher.StatusBarUserSwitcherController;
@@ -34,12 +35,14 @@
 import com.android.systemui.statusbar.window.StatusBarWindowController;
 
 import java.util.Optional;
+import java.util.Set;
 
 import javax.inject.Named;
 
 import dagger.Binds;
 import dagger.Module;
 import dagger.Provides;
+import dagger.multibindings.Multibinds;
 
 /** Dagger module for {@link StatusBarFragmentComponent}. */
 @Module
@@ -48,6 +51,8 @@
     String LIGHTS_OUT_NOTIF_VIEW = "lights_out_notif_view";
     String OPERATOR_NAME_VIEW = "operator_name_view";
     String OPERATOR_NAME_FRAME_VIEW = "operator_name_frame_view";
+    String START_SIDE_CONTENT = "start_side_content";
+    String END_SIDE_CONTENT = "end_side_content";
 
     /** */
     @Provides
@@ -68,6 +73,22 @@
     /** */
     @Provides
     @StatusBarFragmentScope
+    @Named(START_SIDE_CONTENT)
+    static View startSideContent(@RootView PhoneStatusBarView view) {
+        return view.findViewById(R.id.status_bar_start_side_content);
+    }
+
+    /** */
+    @Provides
+    @StatusBarFragmentScope
+    @Named(END_SIDE_CONTENT)
+    static View endSideContent(@RootView PhoneStatusBarView view) {
+        return view.findViewById(R.id.status_bar_end_side_content);
+    }
+
+    /** */
+    @Provides
+    @StatusBarFragmentScope
     @Named(LIGHTS_OUT_NOTIF_VIEW)
     static View provideLightsOutNotifView(@RootView PhoneStatusBarView view) {
         return view.findViewById(R.id.notification_lights_out);
@@ -138,4 +159,8 @@
     static HeadsUpStatusBarView providesHeasdUpStatusBarView(@RootView PhoneStatusBarView view) {
         return view.findViewById(R.id.heads_up_status_bar_view);
     }
+
+    /** */
+    @Multibinds
+    Set<StatusBarBoundsProvider.BoundsChangeListener> boundsChangeListeners();
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/dagger/StatusBarStartablesModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/dagger/StatusBarStartablesModule.kt
new file mode 100644
index 0000000..9003d13
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/dagger/StatusBarStartablesModule.kt
@@ -0,0 +1,32 @@
+/*
+ * 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.phone.fragment.dagger
+
+import com.android.systemui.statusbar.phone.StatusBarBoundsProvider
+import dagger.Binds
+import dagger.Module
+import dagger.multibindings.IntoSet
+
+@Module
+internal interface StatusBarStartablesModule {
+
+    @Binds
+    @IntoSet
+    fun statusBarBoundsCalculator(
+        statusBarBoundsProvider: StatusBarBoundsProvider
+    ): StatusBarFragmentComponent.Startable
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BrightnessMirrorController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BrightnessMirrorController.java
index 337ffdf..86e7456 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BrightnessMirrorController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BrightnessMirrorController.java
@@ -155,6 +155,7 @@
                 .inflate(R.layout.brightness_mirror_container, mStatusBarWindow, false);
         mToggleSliderController = setMirrorLayout();
         mStatusBarWindow.addView(mBrightnessMirror, index);
+        updateResources();
 
         for (int i = 0; i < mBrightnessMirrorListeners.size(); i++) {
             mBrightnessMirrorListeners.valueAt(i).onBrightnessMirrorReinflated(mBrightnessMirror);
diff --git a/packages/SystemUI/src/com/android/systemui/util/ConvenienceExtensions.kt b/packages/SystemUI/src/com/android/systemui/util/ConvenienceExtensions.kt
index 57b3f53..ec7aabb 100644
--- a/packages/SystemUI/src/com/android/systemui/util/ConvenienceExtensions.kt
+++ b/packages/SystemUI/src/com/android/systemui/util/ConvenienceExtensions.kt
@@ -16,7 +16,9 @@
 
 package com.android.systemui.util
 
+import android.graphics.Rect
 import android.util.IndentingPrintWriter
+import android.view.View
 import android.view.ViewGroup
 import java.io.PrintWriter
 
@@ -45,4 +47,12 @@
     if (this is IndentingPrintWriter) increaseIndent()
     block()
     if (this is IndentingPrintWriter) decreaseIndent()
-}
\ No newline at end of file
+}
+
+/** Convenience extension property for [View.getBoundsOnScreen]. */
+val View.boundsOnScreen: Rect
+    get() {
+        val bounds = Rect()
+        getBoundsOnScreen(bounds)
+        return bounds
+    }
diff --git a/packages/SystemUI/src/com/android/systemui/util/NoRemeasureMotionLayout.kt b/packages/SystemUI/src/com/android/systemui/util/NoRemeasureMotionLayout.kt
new file mode 100644
index 0000000..3095d80
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/util/NoRemeasureMotionLayout.kt
@@ -0,0 +1,57 @@
+/*
+ * 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.util
+
+import android.content.Context
+import android.util.AttributeSet
+import android.view.Choreographer
+import androidx.constraintlayout.motion.widget.MotionLayout
+
+/**
+ * [MotionLayout] that avoids remeasuring with the same inputs in the same frame.
+ *
+ * This is important when this view is the child of a view that performs more than one measure pass
+ * (e.g. [LinearLayout] or [ConstraintLayout]). In those cases, if this view is measured with the
+ * same inputs in the same frame, we use the last result.
+ */
+class NoRemeasureMotionLayout @JvmOverloads constructor(
+    context: Context,
+    attrs: AttributeSet?,
+    defStyle: Int = 0
+) : MotionLayout(context, attrs, defStyle) {
+
+    private var lastWidthSpec: Int? = null
+    private var lastHeightSpec: Int? = null
+    private var lastFrame: Long? = null
+
+    override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
+        if (
+            lastWidthSpec == widthMeasureSpec &&
+            lastHeightSpec == heightMeasureSpec &&
+            Choreographer.getMainThreadInstance()?.frameTime == lastFrame
+        ) {
+            setMeasuredDimension(measuredWidth, measuredHeight)
+            return
+        }
+        traceSection("NoRemeasureMotionLayout - measure") {
+            super.onMeasure(widthMeasureSpec, heightMeasureSpec)
+            lastWidthSpec = widthMeasureSpec
+            lastHeightSpec = heightMeasureSpec
+            lastFrame = Choreographer.getMainThreadInstance()?.frameTime
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java b/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
index 83b0022..12597e0 100644
--- a/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
+++ b/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
@@ -54,7 +54,6 @@
 import com.android.systemui.statusbar.policy.UserInfoController;
 import com.android.systemui.tracing.ProtoTracer;
 import com.android.systemui.tracing.nano.SystemUiTraceProto;
-import com.android.wm.shell.ShellCommandHandler;
 import com.android.wm.shell.nano.WmShellTraceProto;
 import com.android.wm.shell.onehanded.OneHanded;
 import com.android.wm.shell.onehanded.OneHandedEventCallback;
@@ -107,7 +106,6 @@
     private final Optional<Pip> mPipOptional;
     private final Optional<SplitScreen> mSplitScreenOptional;
     private final Optional<OneHanded> mOneHandedOptional;
-    private final Optional<ShellCommandHandler> mShellCommandHandler;
 
     private final CommandQueue mCommandQueue;
     private final ConfigurationController mConfigurationController;
@@ -130,7 +128,6 @@
             Optional<Pip> pipOptional,
             Optional<SplitScreen> splitScreenOptional,
             Optional<OneHanded> oneHandedOptional,
-            Optional<ShellCommandHandler> shellCommandHandler,
             CommandQueue commandQueue,
             ConfigurationController configurationController,
             KeyguardStateController keyguardStateController,
@@ -154,7 +151,6 @@
         mOneHandedOptional = oneHandedOptional;
         mWakefulnessLifecycle = wakefulnessLifecycle;
         mProtoTracer = protoTracer;
-        mShellCommandHandler = shellCommandHandler;
         mUserInfoController = userInfoController;
         mSysUiMainExecutor = sysUiMainExecutor;
     }
@@ -325,8 +321,7 @@
     @Override
     public void dump(PrintWriter pw, String[] args) {
         // Handle commands if provided
-        if (mShellCommandHandler.isPresent()
-                && mShellCommandHandler.get().handleCommand(args, pw)) {
+        if (mShell.handleCommand(args, pw)) {
             return;
         }
         // Handle logging commands if provided
@@ -334,8 +329,7 @@
             return;
         }
         // Dump WMShell stuff here if no commands were handled
-        mShellCommandHandler.ifPresent(
-                shellCommandHandler -> shellCommandHandler.dump(pw));
+        mShell.dump(pw);
     }
 
     @Override
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/TextInterpolatorTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/TextInterpolatorTest.kt
index e42d537..603cf3b 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/TextInterpolatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/TextInterpolatorTest.kt
@@ -18,6 +18,7 @@
 
 import android.graphics.Bitmap
 import android.graphics.Canvas
+import android.graphics.Color
 import android.graphics.Typeface
 import android.graphics.fonts.Font
 import android.graphics.fonts.FontFamily
@@ -194,6 +195,128 @@
 
         assertThat(expected.sameAs(actual)).isTrue()
     }
+
+    @Test
+    fun testGlyphCallback_Empty() {
+        val layout = makeLayout(BIDI_TEXT, PAINT, TextDirectionHeuristics.RTL)
+
+        val interp = TextInterpolator(layout).apply {
+            glyphFilter = { glyph, progress ->
+            }
+        }
+        interp.basePaint.set(START_PAINT)
+        interp.onBasePaintModified()
+
+        interp.targetPaint.set(END_PAINT)
+        interp.onTargetPaintModified()
+
+        // Just after created TextInterpolator, it should have 0 progress.
+        val actual = interp.toBitmap(BMP_WIDTH, BMP_HEIGHT)
+        val expected = makeLayout(BIDI_TEXT, START_PAINT, TextDirectionHeuristics.RTL)
+                .toBitmap(BMP_WIDTH, BMP_HEIGHT)
+
+        assertThat(expected.sameAs(actual)).isTrue()
+    }
+
+    @Test
+    fun testGlyphCallback_Xcoordinate() {
+        val layout = makeLayout(BIDI_TEXT, PAINT, TextDirectionHeuristics.RTL)
+
+        val interp = TextInterpolator(layout).apply {
+            glyphFilter = { glyph, progress ->
+                glyph.x += 30f
+            }
+        }
+        interp.basePaint.set(START_PAINT)
+        interp.onBasePaintModified()
+
+        interp.targetPaint.set(END_PAINT)
+        interp.onTargetPaintModified()
+
+        // Just after created TextInterpolator, it should have 0 progress.
+        val actual = interp.toBitmap(BMP_WIDTH, BMP_HEIGHT)
+        val expected = makeLayout(BIDI_TEXT, START_PAINT, TextDirectionHeuristics.RTL)
+                .toBitmap(BMP_WIDTH, BMP_HEIGHT)
+
+        // The glyph position was modified by callback, so the bitmap should not be the same.
+        // We cannot modify the result of StaticLayout, so we cannot expect the exact  bitmaps.
+        assertThat(expected.sameAs(actual)).isFalse()
+    }
+
+    @Test
+    fun testGlyphCallback_Ycoordinate() {
+        val layout = makeLayout(BIDI_TEXT, PAINT, TextDirectionHeuristics.RTL)
+
+        val interp = TextInterpolator(layout).apply {
+            glyphFilter = { glyph, progress ->
+                glyph.y += 30f
+            }
+        }
+        interp.basePaint.set(START_PAINT)
+        interp.onBasePaintModified()
+
+        interp.targetPaint.set(END_PAINT)
+        interp.onTargetPaintModified()
+
+        // Just after created TextInterpolator, it should have 0 progress.
+        val actual = interp.toBitmap(BMP_WIDTH, BMP_HEIGHT)
+        val expected = makeLayout(BIDI_TEXT, START_PAINT, TextDirectionHeuristics.RTL)
+                .toBitmap(BMP_WIDTH, BMP_HEIGHT)
+
+        // The glyph position was modified by callback, so the bitmap should not be the same.
+        // We cannot modify the result of StaticLayout, so we cannot expect the exact  bitmaps.
+        assertThat(expected.sameAs(actual)).isFalse()
+    }
+
+    @Test
+    fun testGlyphCallback_TextSize() {
+        val layout = makeLayout(BIDI_TEXT, PAINT, TextDirectionHeuristics.RTL)
+
+        val interp = TextInterpolator(layout).apply {
+            glyphFilter = { glyph, progress ->
+                glyph.textSize += 10f
+            }
+        }
+        interp.basePaint.set(START_PAINT)
+        interp.onBasePaintModified()
+
+        interp.targetPaint.set(END_PAINT)
+        interp.onTargetPaintModified()
+
+        // Just after created TextInterpolator, it should have 0 progress.
+        val actual = interp.toBitmap(BMP_WIDTH, BMP_HEIGHT)
+        val expected = makeLayout(BIDI_TEXT, START_PAINT, TextDirectionHeuristics.RTL)
+                .toBitmap(BMP_WIDTH, BMP_HEIGHT)
+
+        // The glyph position was modified by callback, so the bitmap should not be the same.
+        // We cannot modify the result of StaticLayout, so we cannot expect the exact  bitmaps.
+        assertThat(expected.sameAs(actual)).isFalse()
+    }
+
+    @Test
+    fun testGlyphCallback_Color() {
+        val layout = makeLayout(BIDI_TEXT, PAINT, TextDirectionHeuristics.RTL)
+
+        val interp = TextInterpolator(layout).apply {
+            glyphFilter = { glyph, progress ->
+                glyph.color = Color.RED
+            }
+        }
+        interp.basePaint.set(START_PAINT)
+        interp.onBasePaintModified()
+
+        interp.targetPaint.set(END_PAINT)
+        interp.onTargetPaintModified()
+
+        // Just after created TextInterpolator, it should have 0 progress.
+        val actual = interp.toBitmap(BMP_WIDTH, BMP_HEIGHT)
+        val expected = makeLayout(BIDI_TEXT, START_PAINT, TextDirectionHeuristics.RTL)
+                .toBitmap(BMP_WIDTH, BMP_HEIGHT)
+
+        // The glyph position was modified by callback, so the bitmap should not be the same.
+        // We cannot modify the result of StaticLayout, so we cannot expect the exact  bitmaps.
+        assertThat(expected.sameAs(actual)).isFalse()
+    }
 }
 
 private fun Layout.toBitmap(width: Int, height: Int) =
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/CombinedShadeHeaderConstraintsTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/CombinedShadeHeaderConstraintsTest.kt
new file mode 100644
index 0000000..0ce9056
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/CombinedShadeHeaderConstraintsTest.kt
@@ -0,0 +1,328 @@
+/*
+ * 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 android.testing.AndroidTestingRunner
+import androidx.constraintlayout.widget.ConstraintSet
+import androidx.constraintlayout.widget.ConstraintSet.PARENT_ID
+import androidx.constraintlayout.widget.ConstraintSet.START
+import androidx.test.filters.SmallTest
+import com.android.systemui.R
+import com.android.systemui.SysuiTestCase
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class CombinedShadeHeaderConstraintsTest : SysuiTestCase() {
+
+    private lateinit var qqsConstraint: ConstraintSet
+    private lateinit var qsConstraint: ConstraintSet
+    private lateinit var largeScreenConstraint: ConstraintSet
+
+    @Before
+    fun setUp() {
+        qqsConstraint = ConstraintSet().apply {
+            load(context, context.resources.getXml(R.xml.qqs_header))
+        }
+        qsConstraint = ConstraintSet().apply {
+            load(context, context.resources.getXml(R.xml.qs_header_new))
+        }
+        largeScreenConstraint = ConstraintSet().apply {
+            load(context, context.resources.getXml(R.xml.large_screen_shade_header))
+        }
+    }
+
+    @Test
+    fun testEdgeElementsAlignedWithGuide_qqs() {
+        with(qqsConstraint) {
+            assertThat(getConstraint(R.id.clock).layout.startToStart).isEqualTo(R.id.begin_guide)
+            assertThat(getConstraint(R.id.clock).layout.horizontalBias).isEqualTo(0f)
+
+            assertThat(getConstraint(R.id.batteryRemainingIcon).layout.endToEnd)
+                .isEqualTo(R.id.end_guide)
+            assertThat(getConstraint(R.id.batteryRemainingIcon).layout.horizontalBias)
+                .isEqualTo(1f)
+
+            assertThat(getConstraint(R.id.privacy_container).layout.endToEnd)
+                .isEqualTo(R.id.end_guide)
+            assertThat(getConstraint(R.id.privacy_container).layout.horizontalBias)
+                .isEqualTo(1f)
+        }
+    }
+
+    @Test
+    fun testClockScale() {
+        with(qqsConstraint.getConstraint(R.id.clock)) {
+            assertThat(transform.scaleX).isEqualTo(1f)
+            assertThat(transform.scaleY).isEqualTo(1f)
+        }
+        with(qsConstraint.getConstraint(R.id.clock)) {
+            assertThat(transform.scaleX).isGreaterThan(1f)
+            assertThat(transform.scaleY).isGreaterThan(1f)
+        }
+    }
+
+    @Test
+    fun testEdgeElementsAlignedWithEdgeOrGuide_qs() {
+        with(qsConstraint) {
+            assertThat(getConstraint(R.id.clock).layout.startToStart).isEqualTo(PARENT_ID)
+            assertThat(getConstraint(R.id.clock).layout.horizontalBias).isEqualTo(0f)
+
+            assertThat(getConstraint(R.id.date).layout.startToStart).isEqualTo(PARENT_ID)
+            assertThat(getConstraint(R.id.date).layout.horizontalBias).isEqualTo(0f)
+
+            assertThat(getConstraint(R.id.batteryRemainingIcon).layout.endToEnd)
+                .isEqualTo(PARENT_ID)
+            assertThat(getConstraint(R.id.batteryRemainingIcon).layout.horizontalBias)
+                .isEqualTo(1f)
+
+            assertThat(getConstraint(R.id.privacy_container).layout.endToEnd)
+                .isEqualTo(R.id.end_guide)
+            assertThat(getConstraint(R.id.privacy_container).layout.horizontalBias).isEqualTo(1f)
+        }
+    }
+
+    @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.privacy_container).layout.endToEnd).isEqualTo(PARENT_ID)
+            assertThat(getConstraint(R.id.privacy_container).layout.horizontalBias).isEqualTo(1f)
+        }
+    }
+
+    @Test
+    fun testCarrierAlpha() {
+        assertThat(qqsConstraint.getConstraint(R.id.carrier_group).propertySet.alpha).isEqualTo(0f)
+        assertThat(qsConstraint.getConstraint(R.id.carrier_group).propertySet.alpha).isEqualTo(1f)
+        assertThat(largeScreenConstraint.getConstraint(R.id.carrier_group).propertySet.alpha)
+            .isEqualTo(1f)
+    }
+
+    @Test
+    fun testPrivacyChipVisibilityConstraints_notVisible() {
+        val changes = CombinedShadeHeadersConstraintManagerImpl
+            .privacyChipVisibilityConstraints(false)
+        changes()
+
+        with(qqsConstraint) {
+            assertThat(getConstraint(R.id.statusIcons).propertySet.alpha).isEqualTo(1f)
+            assertThat(getConstraint(R.id.batteryRemainingIcon).propertySet.alpha).isEqualTo(1f)
+        }
+
+        with(qsConstraint) {
+            assertThat(getConstraint(R.id.statusIcons).propertySet.alpha).isEqualTo(1f)
+            assertThat(getConstraint(R.id.batteryRemainingIcon).propertySet.alpha).isEqualTo(1f)
+        }
+
+        with(largeScreenConstraint) {
+            assertThat(getConstraint(R.id.statusIcons).propertySet.alpha).isEqualTo(1f)
+            assertThat(getConstraint(R.id.batteryRemainingIcon).propertySet.alpha).isEqualTo(1f)
+        }
+    }
+
+    @Test
+    fun testPrivacyChipVisibilityConstraints_visible() {
+        val changes = CombinedShadeHeadersConstraintManagerImpl
+            .privacyChipVisibilityConstraints(true)
+        changes()
+
+        with(qqsConstraint) {
+            assertThat(getConstraint(R.id.statusIcons).propertySet.alpha).isEqualTo(0f)
+            assertThat(getConstraint(R.id.batteryRemainingIcon).propertySet.alpha).isEqualTo(0f)
+        }
+
+        with(qsConstraint) {
+            assertThat(getConstraint(R.id.statusIcons).propertySet.alpha).isEqualTo(1f)
+            assertThat(getConstraint(R.id.batteryRemainingIcon).propertySet.alpha).isEqualTo(1f)
+        }
+
+        with(largeScreenConstraint) {
+            assertThat(getConstraint(R.id.statusIcons).propertySet.alpha).isEqualTo(1f)
+            assertThat(getConstraint(R.id.batteryRemainingIcon).propertySet.alpha).isEqualTo(1f)
+        }
+    }
+
+    @Test
+    fun testEmptyCutoutConstraints() {
+        val changes = CombinedShadeHeadersConstraintManagerImpl.emptyCutoutConstraints()
+        changes()
+
+        // QS and Large Screen don't change with cutouts.
+        assertThat(changes.qsConstraintsChanges).isNull()
+        assertThat(changes.largeScreenConstraintsChanges).isNull()
+
+        with(qqsConstraint) {
+            // In this case, the date is constrained on the end by a Barrier determined by either
+            // privacy or statusIcons
+            assertThat(getConstraint(R.id.date).layout.endToStart).isEqualTo(R.id.barrier)
+            assertThat(getConstraint(R.id.statusIcons).layout.startToEnd).isEqualTo(R.id.date)
+            assertThat(getConstraint(R.id.privacy_container).layout.startToEnd).isEqualTo(R.id.date)
+            assertThat(getConstraint(R.id.barrier).layout.mReferenceIds).asList().containsExactly(
+                R.id.statusIcons,
+                R.id.privacy_container
+            )
+            assertThat(getConstraint(R.id.barrier).layout.mBarrierDirection).isEqualTo(START)
+        }
+    }
+
+    @Test
+    fun testGuidesAreSetInCorrectPosition_largeCutoutSmallerPadding() {
+        val cutoutStart = 100
+        val padding = 10
+        val cutoutEnd = 30
+        val changes = CombinedShadeHeadersConstraintManagerImpl.edgesGuidelinesConstraints(
+            cutoutStart,
+            padding,
+            cutoutEnd,
+            padding
+        )
+        changes()
+
+        with(qqsConstraint) {
+            assertThat(getConstraint(R.id.begin_guide).layout.guideBegin)
+                .isEqualTo(cutoutStart - padding)
+            assertThat(getConstraint(R.id.end_guide).layout.guideEnd)
+                .isEqualTo(cutoutEnd - padding)
+        }
+
+        with(qsConstraint) {
+            assertThat(getConstraint(R.id.begin_guide).layout.guideBegin)
+                .isEqualTo(cutoutStart - padding)
+            assertThat(getConstraint(R.id.end_guide).layout.guideEnd)
+                .isEqualTo(cutoutEnd - padding)
+        }
+
+        assertThat(changes.largeScreenConstraintsChanges).isNull()
+    }
+
+    @Test
+    fun testGuidesAreSetInCorrectPosition_smallCutoutLargerPadding() {
+        val cutoutStart = 5
+        val padding = 10
+        val cutoutEnd = 10
+
+        val changes = CombinedShadeHeadersConstraintManagerImpl.edgesGuidelinesConstraints(
+            cutoutStart,
+            padding,
+            cutoutEnd,
+            padding
+        )
+        changes()
+
+        with(qqsConstraint) {
+            assertThat(getConstraint(R.id.begin_guide).layout.guideBegin).isEqualTo(0)
+            assertThat(getConstraint(R.id.end_guide).layout.guideEnd).isEqualTo(0)
+        }
+
+        with(qsConstraint) {
+            assertThat(getConstraint(R.id.begin_guide).layout.guideBegin).isEqualTo(0)
+            assertThat(getConstraint(R.id.end_guide).layout.guideEnd).isEqualTo(0)
+        }
+
+        assertThat(changes.largeScreenConstraintsChanges).isNull()
+    }
+
+    @Test
+    fun testCenterCutoutConstraints_ltr() {
+        val offsetFromEdge = 400
+        val rtl = false
+
+        val changes = CombinedShadeHeadersConstraintManagerImpl
+            .centerCutoutConstraints(rtl, offsetFromEdge)
+        changes()
+
+        // In LTR, center_left is towards the start and center_right is towards the end
+        with(qqsConstraint) {
+            assertThat(getConstraint(R.id.center_left).layout.guideBegin).isEqualTo(offsetFromEdge)
+            assertThat(getConstraint(R.id.center_right).layout.guideEnd).isEqualTo(offsetFromEdge)
+            assertThat(getConstraint(R.id.date).layout.endToStart).isEqualTo(R.id.center_left)
+            assertThat(getConstraint(R.id.statusIcons).layout.startToEnd)
+                .isEqualTo(R.id.center_right)
+            assertThat(getConstraint(R.id.privacy_container).layout.startToEnd)
+                .isEqualTo(R.id.center_right)
+        }
+
+        with(qsConstraint) {
+            assertThat(getConstraint(R.id.center_left).layout.guideBegin).isEqualTo(offsetFromEdge)
+            assertThat(getConstraint(R.id.center_right).layout.guideEnd).isEqualTo(offsetFromEdge)
+
+            assertThat(getConstraint(R.id.date).layout.endToStart).isNotEqualTo(R.id.center_left)
+            assertThat(getConstraint(R.id.date).layout.endToStart).isNotEqualTo(R.id.center_right)
+
+            assertThat(getConstraint(R.id.statusIcons).layout.startToEnd)
+                .isNotEqualTo(R.id.center_left)
+            assertThat(getConstraint(R.id.statusIcons).layout.startToEnd)
+                .isNotEqualTo(R.id.center_right)
+
+            assertThat(getConstraint(R.id.privacy_container).layout.startToEnd)
+                .isEqualTo(R.id.center_right)
+        }
+
+        assertThat(changes.largeScreenConstraintsChanges).isNull()
+    }
+
+    @Test
+    fun testCenterCutoutConstraints_rtl() {
+        val offsetFromEdge = 400
+        val rtl = true
+
+        val changes = CombinedShadeHeadersConstraintManagerImpl
+            .centerCutoutConstraints(rtl, offsetFromEdge)
+        changes()
+
+        // In RTL, center_left is towards the end and center_right is towards the start
+        with(qqsConstraint) {
+            assertThat(getConstraint(R.id.center_left).layout.guideEnd).isEqualTo(offsetFromEdge)
+            assertThat(getConstraint(R.id.center_right).layout.guideBegin).isEqualTo(offsetFromEdge)
+            assertThat(getConstraint(R.id.date).layout.endToStart).isEqualTo(R.id.center_right)
+            assertThat(getConstraint(R.id.statusIcons).layout.startToEnd)
+                .isEqualTo(R.id.center_left)
+            assertThat(getConstraint(R.id.privacy_container).layout.startToEnd)
+                .isEqualTo(R.id.center_left)
+        }
+
+        with(qsConstraint) {
+            assertThat(getConstraint(R.id.center_left).layout.guideEnd).isEqualTo(offsetFromEdge)
+            assertThat(getConstraint(R.id.center_right).layout.guideBegin).isEqualTo(offsetFromEdge)
+
+            assertThat(getConstraint(R.id.date).layout.endToStart).isNotEqualTo(R.id.center_left)
+            assertThat(getConstraint(R.id.date).layout.endToStart).isNotEqualTo(R.id.center_right)
+
+            assertThat(getConstraint(R.id.statusIcons).layout.startToEnd)
+                .isNotEqualTo(R.id.center_left)
+            assertThat(getConstraint(R.id.statusIcons).layout.startToEnd)
+                .isNotEqualTo(R.id.center_right)
+
+            assertThat(getConstraint(R.id.privacy_container).layout.startToEnd)
+                .isEqualTo(R.id.center_left)
+        }
+
+        assertThat(changes.largeScreenConstraintsChanges).isNull()
+    }
+
+    private operator fun ConstraintsChanges.invoke() {
+        qqsConstraintsChanges?.invoke(qqsConstraint)
+        qsConstraintsChanges?.invoke(qsConstraint)
+        largeScreenConstraintsChanges?.invoke(largeScreenConstraint)
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/ConstraintChangeTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/ConstraintChangeTest.kt
new file mode 100644
index 0000000..9b2e085
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/ConstraintChangeTest.kt
@@ -0,0 +1,85 @@
+/*
+ * 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 android.testing.AndroidTestingRunner
+import androidx.constraintlayout.widget.ConstraintSet
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.util.mockito.mock
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mockito.inOrder
+import org.mockito.Mockito.verify
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class ConstraintChangeTest : SysuiTestCase() {
+
+    @Test
+    fun testSumNonNull() {
+        val mock1: ConstraintChange = mock()
+        val mock2: ConstraintChange = mock()
+
+        val constraintSet = ConstraintSet()
+
+        val sum = mock1 + mock2
+        sum?.invoke(constraintSet)
+
+        val inOrder = inOrder(mock1, mock2)
+        inOrder.verify(mock1).invoke(constraintSet)
+        inOrder.verify(mock2).invoke(constraintSet)
+    }
+
+    @Test
+    fun testSumThisNull() {
+        val mock: ConstraintChange = mock()
+        val constraintSet = ConstraintSet()
+
+        val sum = (null as? ConstraintChange?) + mock
+        sum?.invoke(constraintSet)
+
+        verify(mock).invoke(constraintSet)
+    }
+
+    @Test
+    fun testSumThisNull_notWrapped() {
+        val change: ConstraintChange = {}
+
+        val sum = (null as? ConstraintChange?) + change
+        assertThat(sum).isSameInstanceAs(change)
+    }
+
+    @Test
+    fun testSumOtherNull() {
+        val mock: ConstraintChange = mock()
+        val constraintSet = ConstraintSet()
+
+        val sum = mock + (null as? ConstraintChange?)
+        sum?.invoke(constraintSet)
+
+        verify(mock).invoke(constraintSet)
+    }
+
+    @Test
+    fun testSumOtherNull_notWrapped() {
+        val change: ConstraintChange = {}
+
+        val sum = change + (null as? ConstraintChange?)
+        assertThat(sum).isSameInstanceAs(change)
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/ConstraintChangesTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/ConstraintChangesTest.kt
new file mode 100644
index 0000000..0abb084
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/ConstraintChangesTest.kt
@@ -0,0 +1,75 @@
+/*
+ * 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 android.testing.AndroidTestingRunner
+import androidx.constraintlayout.widget.ConstraintSet
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.util.mockito.mock
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mockito.inOrder
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class ConstraintChangesTest : SysuiTestCase() {
+
+    @Test
+    fun testSumWithoutNulls() {
+        val mockQQS1: ConstraintChange = mock()
+        val mockQS1: ConstraintChange = mock()
+        val mockLS1: ConstraintChange = mock()
+        val mockQQS2: ConstraintChange = mock()
+        val mockQS2: ConstraintChange = mock()
+        val mockLS2: ConstraintChange = mock()
+
+        val changes1 = ConstraintsChanges(mockQQS1, mockQS1, mockLS1)
+        val changes2 = ConstraintsChanges(mockQQS2, mockQS2, mockLS2)
+
+        val sum = changes1 + changes2
+
+        val constraintSet = ConstraintSet()
+        sum.qqsConstraintsChanges?.invoke(constraintSet)
+        sum.qsConstraintsChanges?.invoke(constraintSet)
+        sum.largeScreenConstraintsChanges?.invoke(constraintSet)
+
+        val inOrder = inOrder(mockQQS1, mockQS1, mockLS1, mockQQS2, mockQS2, mockLS2)
+
+        inOrder.verify(mockQQS1).invoke(constraintSet)
+        inOrder.verify(mockQQS2).invoke(constraintSet)
+        inOrder.verify(mockQS1).invoke(constraintSet)
+        inOrder.verify(mockQS2).invoke(constraintSet)
+        inOrder.verify(mockLS1).invoke(constraintSet)
+        inOrder.verify(mockLS2).invoke(constraintSet)
+    }
+
+    @Test
+    fun testSumWithSomeNulls() {
+        val mockQQS: ConstraintChange = mock()
+        val mockQS: ConstraintChange = mock()
+
+        val changes1 = ConstraintsChanges(mockQQS, null, null)
+        val changes2 = ConstraintsChanges(null, mockQS, null)
+
+        val sum = changes1 + changes2
+
+        assertThat(sum.qqsConstraintsChanges).isSameInstanceAs(mockQQS)
+        assertThat(sum.qsConstraintsChanges).isSameInstanceAs(mockQS)
+        assertThat(sum.largeScreenConstraintsChanges).isNull()
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/LargeScreenShadeHeaderControllerCombinedTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/LargeScreenShadeHeaderControllerCombinedTest.kt
new file mode 100644
index 0000000..ed1a13b
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/LargeScreenShadeHeaderControllerCombinedTest.kt
@@ -0,0 +1,659 @@
+/*
+ * 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 android.content.Context
+import android.content.res.Resources
+import android.content.res.XmlResourceParser
+import android.graphics.Rect
+import android.testing.AndroidTestingRunner
+import android.view.DisplayCutout
+import android.view.View
+import android.view.WindowInsets
+import android.widget.TextView
+import androidx.constraintlayout.motion.widget.MotionLayout
+import androidx.constraintlayout.widget.ConstraintSet
+import androidx.test.filters.SmallTest
+import com.android.systemui.R
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.animation.ShadeInterpolation
+import com.android.systemui.battery.BatteryMeterView
+import com.android.systemui.battery.BatteryMeterViewController
+import com.android.systemui.dump.DumpManager
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags
+import com.android.systemui.qs.ChipVisibilityListener
+import com.android.systemui.qs.HeaderPrivacyIconsController
+import com.android.systemui.qs.carrier.QSCarrierGroup
+import com.android.systemui.qs.carrier.QSCarrierGroupController
+import com.android.systemui.shade.LargeScreenShadeHeaderController.Companion.HEADER_TRANSITION_ID
+import com.android.systemui.shade.LargeScreenShadeHeaderController.Companion.LARGE_SCREEN_HEADER_CONSTRAINT
+import com.android.systemui.shade.LargeScreenShadeHeaderController.Companion.LARGE_SCREEN_HEADER_TRANSITION_ID
+import com.android.systemui.shade.LargeScreenShadeHeaderController.Companion.QQS_HEADER_CONSTRAINT
+import com.android.systemui.shade.LargeScreenShadeHeaderController.Companion.QS_HEADER_CONSTRAINT
+import com.android.systemui.statusbar.phone.StatusBarContentInsetsProvider
+import com.android.systemui.statusbar.phone.StatusBarIconController
+import com.android.systemui.statusbar.phone.StatusIconContainer
+import com.android.systemui.statusbar.policy.FakeConfigurationController
+import com.android.systemui.statusbar.policy.VariableDateView
+import com.android.systemui.statusbar.policy.VariableDateViewController
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.capture
+import com.android.systemui.util.mockito.eq
+import com.android.systemui.util.mockito.mock
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Answers
+import org.mockito.ArgumentCaptor
+import org.mockito.ArgumentMatchers
+import org.mockito.Mock
+import org.mockito.Mockito.anyBoolean
+import org.mockito.Mockito.anyFloat
+import org.mockito.Mockito.anyInt
+import org.mockito.Mockito.clearInvocations
+import org.mockito.Mockito.inOrder
+import org.mockito.Mockito.never
+import org.mockito.Mockito.verify
+import org.mockito.Mockito.`when` as whenever
+import org.mockito.junit.MockitoJUnit
+
+private val EMPTY_CHANGES = ConstraintsChanges()
+
+/**
+ * Tests for [LargeScreenShadeHeaderController] when [Flags.COMBINED_QS_HEADERS] is `true`.
+ *
+ * Once that flag is removed, this class will be combined with
+ * [LargeScreenShadeHeaderControllerTest].
+ */
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class LargeScreenShadeHeaderControllerCombinedTest : SysuiTestCase() {
+
+    @Mock
+    private lateinit var statusIcons: StatusIconContainer
+    @Mock
+    private lateinit var statusBarIconController: StatusBarIconController
+    @Mock
+    private lateinit var qsCarrierGroupController: QSCarrierGroupController
+    @Mock
+    private lateinit var qsCarrierGroupControllerBuilder: QSCarrierGroupController.Builder
+    @Mock
+    private lateinit var featureFlags: FeatureFlags
+    @Mock
+    private lateinit var clock: TextView
+    @Mock
+    private lateinit var date: VariableDateView
+    @Mock
+    private lateinit var carrierGroup: QSCarrierGroup
+    @Mock
+    private lateinit var batteryMeterView: BatteryMeterView
+    @Mock
+    private lateinit var batteryMeterViewController: BatteryMeterViewController
+    @Mock
+    private lateinit var privacyIconsController: HeaderPrivacyIconsController
+    @Mock
+    private lateinit var insetsProvider: StatusBarContentInsetsProvider
+    @Mock
+    private lateinit var variableDateViewControllerFactory: VariableDateViewController.Factory
+    @Mock
+    private lateinit var variableDateViewController: VariableDateViewController
+    @Mock
+    private lateinit var dumpManager: DumpManager
+    @Mock
+    private lateinit var combinedShadeHeadersConstraintManager:
+        CombinedShadeHeadersConstraintManager
+
+    @Mock
+    private lateinit var mockedContext: Context
+    @Mock(answer = Answers.RETURNS_MOCKS)
+    private lateinit var view: MotionLayout
+
+    @Mock
+    private lateinit var qqsConstraints: ConstraintSet
+    @Mock
+    private lateinit var qsConstraints: ConstraintSet
+    @Mock
+    private lateinit var largeScreenConstraints: ConstraintSet
+
+    @JvmField @Rule
+    val mockitoRule = MockitoJUnit.rule()
+    var viewVisibility = View.GONE
+
+    private lateinit var controller: LargeScreenShadeHeaderController
+    private lateinit var carrierIconSlots: List<String>
+    private val configurationController = FakeConfigurationController()
+
+    @Before
+    fun setUp() {
+        whenever<TextView>(view.findViewById(R.id.clock)).thenReturn(clock)
+        whenever(clock.context).thenReturn(mockedContext)
+
+        whenever<TextView>(view.findViewById(R.id.date)).thenReturn(date)
+        whenever(date.context).thenReturn(mockedContext)
+        whenever(variableDateViewControllerFactory.create(any()))
+            .thenReturn(variableDateViewController)
+
+        whenever<QSCarrierGroup>(view.findViewById(R.id.carrier_group)).thenReturn(carrierGroup)
+        whenever<BatteryMeterView>(view.findViewById(R.id.batteryRemainingIcon))
+            .thenReturn(batteryMeterView)
+
+        whenever<StatusIconContainer>(view.findViewById(R.id.statusIcons)).thenReturn(statusIcons)
+        whenever(statusIcons.context).thenReturn(context)
+
+        whenever(qsCarrierGroupControllerBuilder.setQSCarrierGroup(any()))
+            .thenReturn(qsCarrierGroupControllerBuilder)
+        whenever(qsCarrierGroupControllerBuilder.build()).thenReturn(qsCarrierGroupController)
+
+        whenever(view.context).thenReturn(context)
+        whenever(view.resources).thenReturn(context.resources)
+        whenever(view.setVisibility(ArgumentMatchers.anyInt())).then {
+            viewVisibility = it.arguments[0] as Int
+            null
+        }
+        whenever(view.visibility).thenAnswer { _ -> viewVisibility }
+
+        whenever(featureFlags.isEnabled(Flags.COMBINED_QS_HEADERS)).thenReturn(true)
+        whenever(featureFlags.isEnabled(Flags.NEW_HEADER)).thenReturn(true)
+
+        setUpDefaultInsets()
+        setUpMotionLayout(view)
+
+        controller = LargeScreenShadeHeaderController(
+            view,
+            statusBarIconController,
+            privacyIconsController,
+            insetsProvider,
+            configurationController,
+            variableDateViewControllerFactory,
+            batteryMeterViewController,
+            dumpManager,
+            featureFlags,
+            qsCarrierGroupControllerBuilder,
+            combinedShadeHeadersConstraintManager
+        )
+        whenever(view.isAttachedToWindow).thenReturn(true)
+        controller.init()
+        carrierIconSlots = listOf(
+            context.getString(com.android.internal.R.string.status_bar_mobile))
+    }
+
+    @Test
+    fun testCorrectConstraints() {
+        val captor = ArgumentCaptor.forClass(XmlResourceParser::class.java)
+
+        verify(qqsConstraints).load(eq(context), capture(captor))
+        assertThat(captor.value.getResId()).isEqualTo(R.xml.qqs_header)
+
+        verify(qsConstraints).load(eq(context), capture(captor))
+        assertThat(captor.value.getResId()).isEqualTo(R.xml.qs_header_new)
+
+        verify(largeScreenConstraints).load(eq(context), capture(captor))
+        assertThat(captor.value.getResId()).isEqualTo(R.xml.large_screen_shade_header)
+    }
+
+    @Test
+    fun testControllersCreatedAndInitialized() {
+        verify(variableDateViewController).init()
+
+        verify(batteryMeterViewController).init()
+        verify(batteryMeterViewController).ignoreTunerUpdates()
+        verify(batteryMeterView).setPercentShowMode(BatteryMeterView.MODE_ESTIMATE)
+
+        val inOrder = inOrder(qsCarrierGroupControllerBuilder)
+        inOrder.verify(qsCarrierGroupControllerBuilder).setQSCarrierGroup(carrierGroup)
+        inOrder.verify(qsCarrierGroupControllerBuilder).build()
+    }
+
+    @Test
+    fun testClockPivotLtr() {
+        val width = 200
+        whenever(clock.width).thenReturn(width)
+        whenever(clock.isLayoutRtl).thenReturn(false)
+
+        val captor = ArgumentCaptor.forClass(View.OnLayoutChangeListener::class.java)
+        verify(clock).addOnLayoutChangeListener(capture(captor))
+
+        captor.value.onLayoutChange(clock, 0, 1, 2, 3, 4, 5, 6, 7)
+        verify(clock).pivotX = 0f
+    }
+
+    @Test
+    fun testClockPivotRtl() {
+        val width = 200
+        whenever(clock.width).thenReturn(width)
+        whenever(clock.isLayoutRtl).thenReturn(true)
+
+        val captor = ArgumentCaptor.forClass(View.OnLayoutChangeListener::class.java)
+        verify(clock).addOnLayoutChangeListener(capture(captor))
+
+        captor.value.onLayoutChange(clock, 0, 1, 2, 3, 4, 5, 6, 7)
+        verify(clock).pivotX = width.toFloat()
+    }
+
+    @Test
+    fun testShadeExpanded_true() {
+        // When shade is expanded, view should be visible regardless of largeScreenActive
+        controller.largeScreenActive = false
+        controller.qsVisible = true
+        assertThat(viewVisibility).isEqualTo(View.VISIBLE)
+
+        controller.largeScreenActive = true
+        assertThat(viewVisibility).isEqualTo(View.VISIBLE)
+    }
+
+    @Test
+    fun testShadeExpanded_false() {
+        // When shade is not expanded, view should be invisible regardless of largeScreenActive
+        controller.largeScreenActive = false
+        controller.qsVisible = false
+        assertThat(viewVisibility).isEqualTo(View.INVISIBLE)
+
+        controller.largeScreenActive = true
+        assertThat(viewVisibility).isEqualTo(View.INVISIBLE)
+    }
+
+    @Test
+    fun testLargeScreenActive_true() {
+        controller.largeScreenActive = false // Make sure there's a change
+        clearInvocations(view)
+
+        controller.largeScreenActive = true
+
+        verify(view).setTransition(LARGE_SCREEN_HEADER_TRANSITION_ID)
+    }
+
+    @Test
+    fun testLargeScreenActive_false() {
+        controller.largeScreenActive = true // Make sure there's a change
+        clearInvocations(view)
+
+        controller.largeScreenActive = false
+
+        verify(view).setTransition(HEADER_TRANSITION_ID)
+    }
+
+    @Test
+    fun testShadeExpandedFraction() {
+        // View needs to be visible for this to actually take effect
+        controller.qsVisible = true
+
+        clearInvocations(view)
+        controller.shadeExpandedFraction = 0.3f
+        verify(view).alpha = ShadeInterpolation.getContentAlpha(0.3f)
+
+        clearInvocations(view)
+        controller.shadeExpandedFraction = 1f
+        verify(view).alpha = ShadeInterpolation.getContentAlpha(1f)
+
+        clearInvocations(view)
+        controller.shadeExpandedFraction = 0f
+        verify(view).alpha = ShadeInterpolation.getContentAlpha(0f)
+    }
+
+    @Test
+    fun testQsExpandedFraction_headerTransition() {
+        controller.qsVisible = true
+        controller.largeScreenActive = false
+
+        clearInvocations(view)
+        controller.qsExpandedFraction = 0.3f
+        verify(view).progress = 0.3f
+    }
+
+    @Test
+    fun testQsExpandedFraction_largeScreen() {
+        controller.qsVisible = true
+        controller.largeScreenActive = true
+
+        clearInvocations(view)
+        controller.qsExpandedFraction = 0.3f
+        verify(view, never()).progress = anyFloat()
+    }
+
+    @Test
+    fun testScrollY_headerTransition() {
+        controller.largeScreenActive = false
+
+        clearInvocations(view)
+        controller.qsScrollY = 20
+        verify(view).scrollY = 20
+    }
+
+    @Test
+    fun testScrollY_largeScreen() {
+        controller.largeScreenActive = true
+
+        clearInvocations(view)
+        controller.qsScrollY = 20
+        verify(view, never()).scrollY = anyInt()
+    }
+
+    @Test
+    fun testPrivacyChipVisibilityChanged_visible_changesCorrectConstraints() {
+        val chipVisibleChanges = createMockConstraintChanges()
+        val chipNotVisibleChanges = createMockConstraintChanges()
+
+        whenever(combinedShadeHeadersConstraintManager.privacyChipVisibilityConstraints(true))
+            .thenReturn(chipVisibleChanges)
+        whenever(combinedShadeHeadersConstraintManager.privacyChipVisibilityConstraints(false))
+            .thenReturn(chipNotVisibleChanges)
+
+        val captor = ArgumentCaptor.forClass(ChipVisibilityListener::class.java)
+        verify(privacyIconsController).chipVisibilityListener = capture(captor)
+
+        captor.value.onChipVisibilityRefreshed(true)
+
+        verify(chipVisibleChanges.qqsConstraintsChanges)!!.invoke(qqsConstraints)
+        verify(chipVisibleChanges.qsConstraintsChanges)!!.invoke(qsConstraints)
+        verify(chipVisibleChanges.largeScreenConstraintsChanges)!!.invoke(largeScreenConstraints)
+
+        verify(chipNotVisibleChanges.qqsConstraintsChanges, never())!!.invoke(any())
+        verify(chipNotVisibleChanges.qsConstraintsChanges, never())!!.invoke(any())
+        verify(chipNotVisibleChanges.largeScreenConstraintsChanges, never())!!.invoke(any())
+    }
+
+    @Test
+    fun testPrivacyChipVisibilityChanged_notVisible_changesCorrectConstraints() {
+        val chipVisibleChanges = createMockConstraintChanges()
+        val chipNotVisibleChanges = createMockConstraintChanges()
+
+        whenever(combinedShadeHeadersConstraintManager.privacyChipVisibilityConstraints(true))
+            .thenReturn(chipVisibleChanges)
+        whenever(combinedShadeHeadersConstraintManager.privacyChipVisibilityConstraints(false))
+            .thenReturn(chipNotVisibleChanges)
+
+        val captor = ArgumentCaptor.forClass(ChipVisibilityListener::class.java)
+        verify(privacyIconsController).chipVisibilityListener = capture(captor)
+
+        captor.value.onChipVisibilityRefreshed(false)
+
+        verify(chipVisibleChanges.qqsConstraintsChanges, never())!!.invoke(qqsConstraints)
+        verify(chipVisibleChanges.qsConstraintsChanges, never())!!.invoke(qsConstraints)
+        verify(chipVisibleChanges.largeScreenConstraintsChanges, never())!!
+            .invoke(largeScreenConstraints)
+
+        verify(chipNotVisibleChanges.qqsConstraintsChanges)!!.invoke(any())
+        verify(chipNotVisibleChanges.qsConstraintsChanges)!!.invoke(any())
+        verify(chipNotVisibleChanges.largeScreenConstraintsChanges)!!.invoke(any())
+    }
+
+    @Test
+    fun testInsetsGuides_ltr() {
+        whenever(view.isLayoutRtl).thenReturn(false)
+        val captor = ArgumentCaptor.forClass(View.OnApplyWindowInsetsListener::class.java)
+        verify(view).setOnApplyWindowInsetsListener(capture(captor))
+        val mockConstraintsChanges = createMockConstraintChanges()
+
+        val (insetLeft, insetRight) = 30 to 40
+        val (paddingStart, paddingEnd) = 10 to 20
+        whenever(view.paddingStart).thenReturn(paddingStart)
+        whenever(view.paddingEnd).thenReturn(paddingEnd)
+
+        mockInsetsProvider(insetLeft to insetRight, false)
+
+        whenever(combinedShadeHeadersConstraintManager
+            .edgesGuidelinesConstraints(anyInt(), anyInt(), anyInt(), anyInt())
+        ).thenReturn(mockConstraintsChanges)
+
+        captor.value.onApplyWindowInsets(view, createWindowInsets())
+
+        verify(combinedShadeHeadersConstraintManager)
+            .edgesGuidelinesConstraints(insetLeft, paddingStart, insetRight, paddingEnd)
+
+        verify(mockConstraintsChanges.qqsConstraintsChanges)!!.invoke(any())
+        verify(mockConstraintsChanges.qsConstraintsChanges)!!.invoke(any())
+        verify(mockConstraintsChanges.largeScreenConstraintsChanges)!!.invoke(any())
+    }
+
+    @Test
+    fun testInsetsGuides_rtl() {
+        whenever(view.isLayoutRtl).thenReturn(true)
+        val captor = ArgumentCaptor.forClass(View.OnApplyWindowInsetsListener::class.java)
+        verify(view).setOnApplyWindowInsetsListener(capture(captor))
+        val mockConstraintsChanges = createMockConstraintChanges()
+
+        val (insetLeft, insetRight) = 30 to 40
+        val (paddingStart, paddingEnd) = 10 to 20
+        whenever(view.paddingStart).thenReturn(paddingStart)
+        whenever(view.paddingEnd).thenReturn(paddingEnd)
+
+        mockInsetsProvider(insetLeft to insetRight, false)
+
+        whenever(combinedShadeHeadersConstraintManager
+            .edgesGuidelinesConstraints(anyInt(), anyInt(), anyInt(), anyInt())
+        ).thenReturn(mockConstraintsChanges)
+
+        captor.value.onApplyWindowInsets(view, createWindowInsets())
+
+        verify(combinedShadeHeadersConstraintManager)
+            .edgesGuidelinesConstraints(insetRight, paddingStart, insetLeft, paddingEnd)
+
+        verify(mockConstraintsChanges.qqsConstraintsChanges)!!.invoke(any())
+        verify(mockConstraintsChanges.qsConstraintsChanges)!!.invoke(any())
+        verify(mockConstraintsChanges.largeScreenConstraintsChanges)!!.invoke(any())
+    }
+
+    @Test
+    fun testNullCutout() {
+        val captor = ArgumentCaptor.forClass(View.OnApplyWindowInsetsListener::class.java)
+        verify(view).setOnApplyWindowInsetsListener(capture(captor))
+        val mockConstraintsChanges = createMockConstraintChanges()
+
+        whenever(combinedShadeHeadersConstraintManager.emptyCutoutConstraints())
+            .thenReturn(mockConstraintsChanges)
+
+        captor.value.onApplyWindowInsets(view, createWindowInsets(null))
+
+        verify(combinedShadeHeadersConstraintManager).emptyCutoutConstraints()
+        verify(combinedShadeHeadersConstraintManager, never())
+            .centerCutoutConstraints(anyBoolean(), anyInt())
+
+        verify(mockConstraintsChanges.qqsConstraintsChanges)!!.invoke(any())
+        verify(mockConstraintsChanges.qsConstraintsChanges)!!.invoke(any())
+        verify(mockConstraintsChanges.largeScreenConstraintsChanges)!!.invoke(any())
+    }
+
+    @Test
+    fun testEmptyCutout() {
+        val captor = ArgumentCaptor.forClass(View.OnApplyWindowInsetsListener::class.java)
+        verify(view).setOnApplyWindowInsetsListener(capture(captor))
+        val mockConstraintsChanges = createMockConstraintChanges()
+
+        whenever(combinedShadeHeadersConstraintManager.emptyCutoutConstraints())
+            .thenReturn(mockConstraintsChanges)
+
+        captor.value.onApplyWindowInsets(view, createWindowInsets())
+
+        verify(combinedShadeHeadersConstraintManager).emptyCutoutConstraints()
+        verify(combinedShadeHeadersConstraintManager, never())
+            .centerCutoutConstraints(anyBoolean(), anyInt())
+
+        verify(mockConstraintsChanges.qqsConstraintsChanges)!!.invoke(any())
+        verify(mockConstraintsChanges.qsConstraintsChanges)!!.invoke(any())
+        verify(mockConstraintsChanges.largeScreenConstraintsChanges)!!.invoke(any())
+    }
+
+    @Test
+    fun testCornerCutout_emptyRect() {
+        val captor = ArgumentCaptor.forClass(View.OnApplyWindowInsetsListener::class.java)
+        verify(view).setOnApplyWindowInsetsListener(capture(captor))
+        val mockConstraintsChanges = createMockConstraintChanges()
+
+        mockInsetsProvider(0 to 0, true)
+
+        whenever(combinedShadeHeadersConstraintManager.emptyCutoutConstraints())
+            .thenReturn(mockConstraintsChanges)
+
+        captor.value.onApplyWindowInsets(view, createWindowInsets())
+
+        verify(combinedShadeHeadersConstraintManager).emptyCutoutConstraints()
+        verify(combinedShadeHeadersConstraintManager, never())
+            .centerCutoutConstraints(anyBoolean(), anyInt())
+
+        verify(mockConstraintsChanges.qqsConstraintsChanges)!!.invoke(any())
+        verify(mockConstraintsChanges.qsConstraintsChanges)!!.invoke(any())
+        verify(mockConstraintsChanges.largeScreenConstraintsChanges)!!.invoke(any())
+    }
+
+    @Test
+    fun testCornerCutout_nonEmptyRect() {
+        val captor = ArgumentCaptor.forClass(View.OnApplyWindowInsetsListener::class.java)
+        verify(view).setOnApplyWindowInsetsListener(capture(captor))
+        val mockConstraintsChanges = createMockConstraintChanges()
+
+        mockInsetsProvider(0 to 0, true)
+
+        whenever(combinedShadeHeadersConstraintManager.emptyCutoutConstraints())
+            .thenReturn(mockConstraintsChanges)
+
+        captor.value.onApplyWindowInsets(view, createWindowInsets(Rect(1, 2, 3, 4)))
+
+        verify(combinedShadeHeadersConstraintManager).emptyCutoutConstraints()
+        verify(combinedShadeHeadersConstraintManager, never())
+            .centerCutoutConstraints(anyBoolean(), anyInt())
+
+        verify(mockConstraintsChanges.qqsConstraintsChanges)!!.invoke(any())
+        verify(mockConstraintsChanges.qsConstraintsChanges)!!.invoke(any())
+        verify(mockConstraintsChanges.largeScreenConstraintsChanges)!!.invoke(any())
+    }
+
+    @Test
+    fun testTopCutout_ltr() {
+        val width = 100
+        val paddingLeft = 10
+        val paddingRight = 20
+        val cutoutWidth = 30
+
+        whenever(view.isLayoutRtl).thenReturn(false)
+        whenever(view.width).thenReturn(width)
+        whenever(view.paddingLeft).thenReturn(paddingLeft)
+        whenever(view.paddingRight).thenReturn(paddingRight)
+
+        val captor = ArgumentCaptor.forClass(View.OnApplyWindowInsetsListener::class.java)
+        verify(view).setOnApplyWindowInsetsListener(capture(captor))
+        val mockConstraintsChanges = createMockConstraintChanges()
+
+        mockInsetsProvider(0 to 0, false)
+
+        whenever(combinedShadeHeadersConstraintManager
+            .centerCutoutConstraints(anyBoolean(), anyInt())
+        ).thenReturn(mockConstraintsChanges)
+
+        captor.value.onApplyWindowInsets(view, createWindowInsets(Rect(0, 0, cutoutWidth, 1)))
+
+        verify(combinedShadeHeadersConstraintManager, never()).emptyCutoutConstraints()
+        val offset = (width - paddingLeft - paddingRight - cutoutWidth) / 2
+        verify(combinedShadeHeadersConstraintManager).centerCutoutConstraints(false, offset)
+
+        verify(mockConstraintsChanges.qqsConstraintsChanges)!!.invoke(any())
+        verify(mockConstraintsChanges.qsConstraintsChanges)!!.invoke(any())
+        verify(mockConstraintsChanges.largeScreenConstraintsChanges)!!.invoke(any())
+    }
+
+    @Test
+    fun testTopCutout_rtl() {
+        val width = 100
+        val paddingLeft = 10
+        val paddingRight = 20
+        val cutoutWidth = 30
+
+        whenever(view.isLayoutRtl).thenReturn(true)
+        whenever(view.width).thenReturn(width)
+        whenever(view.paddingLeft).thenReturn(paddingLeft)
+        whenever(view.paddingRight).thenReturn(paddingRight)
+
+        val captor = ArgumentCaptor.forClass(View.OnApplyWindowInsetsListener::class.java)
+        verify(view).setOnApplyWindowInsetsListener(capture(captor))
+        val mockConstraintsChanges = createMockConstraintChanges()
+
+        mockInsetsProvider(0 to 0, false)
+
+        whenever(combinedShadeHeadersConstraintManager
+            .centerCutoutConstraints(anyBoolean(), anyInt())
+        ).thenReturn(mockConstraintsChanges)
+
+        captor.value.onApplyWindowInsets(view, createWindowInsets(Rect(0, 0, cutoutWidth, 1)))
+
+        verify(combinedShadeHeadersConstraintManager, never()).emptyCutoutConstraints()
+        val offset = (width - paddingLeft - paddingRight - cutoutWidth) / 2
+        verify(combinedShadeHeadersConstraintManager).centerCutoutConstraints(true, offset)
+
+        verify(mockConstraintsChanges.qqsConstraintsChanges)!!.invoke(any())
+        verify(mockConstraintsChanges.qsConstraintsChanges)!!.invoke(any())
+        verify(mockConstraintsChanges.largeScreenConstraintsChanges)!!.invoke(any())
+    }
+
+    private fun createWindowInsets(
+        topCutout: Rect? = Rect()
+    ): WindowInsets {
+        val windowInsets: WindowInsets = mock()
+        val displayCutout: DisplayCutout = mock()
+        whenever(windowInsets.displayCutout)
+            .thenReturn(if (topCutout != null) displayCutout else null)
+        whenever(displayCutout.boundingRectTop).thenReturn(topCutout)
+
+        return windowInsets
+    }
+
+    private fun mockInsetsProvider(
+        insets: Pair<Int, Int> = 0 to 0,
+        cornerCutout: Boolean = false,
+    ) {
+        whenever(insetsProvider.getStatusBarContentInsetsForCurrentRotation())
+            .thenReturn(insets.toAndroidPair())
+        whenever(insetsProvider.currentRotationHasCornerCutout()).thenReturn(cornerCutout)
+    }
+
+    private fun createMockConstraintChanges(): ConstraintsChanges {
+        return ConstraintsChanges(mock(), mock(), mock())
+    }
+
+    private fun XmlResourceParser.getResId(): Int {
+        return Resources.getAttributeSetSourceResId(this)
+    }
+
+    private fun setUpMotionLayout(motionLayout: MotionLayout) {
+        whenever(motionLayout.getConstraintSet(QQS_HEADER_CONSTRAINT)).thenReturn(qqsConstraints)
+        whenever(motionLayout.getConstraintSet(QS_HEADER_CONSTRAINT)).thenReturn(qsConstraints)
+        whenever(motionLayout.getConstraintSet(LARGE_SCREEN_HEADER_CONSTRAINT))
+            .thenReturn(largeScreenConstraints)
+    }
+
+    private fun setUpDefaultInsets() {
+        whenever(combinedShadeHeadersConstraintManager
+            .edgesGuidelinesConstraints(anyInt(), anyInt(), anyInt(), anyInt())
+        ).thenReturn(EMPTY_CHANGES)
+        whenever(combinedShadeHeadersConstraintManager.emptyCutoutConstraints())
+            .thenReturn(EMPTY_CHANGES)
+        whenever(combinedShadeHeadersConstraintManager
+            .centerCutoutConstraints(anyBoolean(), anyInt())
+        ).thenReturn(EMPTY_CHANGES)
+        whenever(combinedShadeHeadersConstraintManager
+            .privacyChipVisibilityConstraints(anyBoolean())
+        ).thenReturn(EMPTY_CHANGES)
+        whenever(insetsProvider.getStatusBarContentInsetsForCurrentRotation())
+            .thenReturn(Pair(0, 0).toAndroidPair())
+        whenever(insetsProvider.currentRotationHasCornerCutout()).thenReturn(false)
+    }
+
+    private fun<T, U> Pair<T, U>.toAndroidPair(): android.util.Pair<T, U> {
+        return android.util.Pair(first, second)
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LargeScreenShadeHeaderControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/LargeScreenShadeHeaderControllerTest.kt
similarity index 75%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LargeScreenShadeHeaderControllerTest.kt
rename to packages/SystemUI/tests/src/com/android/systemui/shade/LargeScreenShadeHeaderControllerTest.kt
index 8066401..02b26db 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LargeScreenShadeHeaderControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/LargeScreenShadeHeaderControllerTest.kt
@@ -1,10 +1,8 @@
-package com.android.systemui.statusbar.phone
+package com.android.systemui.shade
 
 import android.app.StatusBarManager
 import android.content.Context
-import android.content.res.TypedArray
 import android.testing.AndroidTestingRunner
-import android.util.TypedValue.COMPLEX_UNIT_PX
 import android.view.View
 import android.widget.TextView
 import androidx.test.filters.SmallTest
@@ -19,19 +17,24 @@
 import com.android.systemui.qs.HeaderPrivacyIconsController
 import com.android.systemui.qs.carrier.QSCarrierGroup
 import com.android.systemui.qs.carrier.QSCarrierGroupController
+import com.android.systemui.statusbar.phone.StatusBarContentInsetsProvider
+import com.android.systemui.statusbar.phone.StatusBarIconController
+import com.android.systemui.statusbar.phone.StatusIconContainer
 import com.android.systemui.statusbar.policy.FakeConfigurationController
+import com.android.systemui.statusbar.policy.VariableDateViewController
+import com.android.systemui.util.mockito.any
 import com.google.common.truth.Truth.assertThat
+import org.junit.After
 import org.junit.Before
 import org.junit.Rule
 import org.junit.Test
 import org.junit.runner.RunWith
-import org.mockito.ArgumentMatchers.any
 import org.mockito.ArgumentMatchers.anyInt
 import org.mockito.Mock
-import org.mockito.Mockito.clearInvocations
 import org.mockito.Mockito.verify
-import org.mockito.junit.MockitoJUnit
+import org.mockito.Mockito.verifyZeroInteractions
 import org.mockito.Mockito.`when` as whenever
+import org.mockito.junit.MockitoJUnit
 
 @SmallTest
 @RunWith(AndroidTestingRunner::class)
@@ -49,10 +52,14 @@
     @Mock private lateinit var batteryMeterView: BatteryMeterView
     @Mock private lateinit var batteryMeterViewController: BatteryMeterViewController
     @Mock private lateinit var privacyIconsController: HeaderPrivacyIconsController
+    @Mock private lateinit var insetsProvider: StatusBarContentInsetsProvider
+    @Mock private lateinit var variableDateViewControllerFactory: VariableDateViewController.Factory
+    @Mock private lateinit var variableDateViewController: VariableDateViewController
     @Mock private lateinit var dumpManager: DumpManager
+    @Mock private lateinit var combinedShadeHeadersConstraintManager:
+        CombinedShadeHeadersConstraintManager
 
     @Mock private lateinit var mockedContext: Context
-    @Mock private lateinit var typedArray: TypedArray
 
     @JvmField @Rule val mockitoRule = MockitoJUnit.rule()
     var viewVisibility = View.GONE
@@ -65,7 +72,6 @@
     fun setup() {
         whenever<TextView>(view.findViewById(R.id.clock)).thenReturn(clock)
         whenever(clock.context).thenReturn(mockedContext)
-        whenever(mockedContext.obtainStyledAttributes(anyInt(), any())).thenReturn(typedArray)
         whenever<TextView>(view.findViewById(R.id.date)).thenReturn(date)
         whenever(date.context).thenReturn(mockedContext)
         whenever<QSCarrierGroup>(view.findViewById(R.id.carrier_group)).thenReturn(carrierGroup)
@@ -73,6 +79,7 @@
                 .thenReturn(batteryMeterView)
         whenever<StatusIconContainer>(view.findViewById(R.id.statusIcons)).thenReturn(statusIcons)
         whenever(view.context).thenReturn(context)
+        whenever(view.resources).thenReturn(context.resources)
         whenever(statusIcons.context).thenReturn(context)
         whenever(qsCarrierGroupControllerBuilder.setQSCarrierGroup(any()))
                 .thenReturn(qsCarrierGroupControllerBuilder)
@@ -82,27 +89,39 @@
             null
         }
         whenever(view.visibility).thenAnswer { _ -> viewVisibility }
+        whenever(variableDateViewControllerFactory.create(any()))
+            .thenReturn(variableDateViewController)
         whenever(featureFlags.isEnabled(Flags.COMBINED_QS_HEADERS)).thenReturn(false)
         mLargeScreenShadeHeaderController = LargeScreenShadeHeaderController(
                 view,
                 statusBarIconController,
                 privacyIconsController,
+                insetsProvider,
                 configurationController,
-                qsCarrierGroupControllerBuilder,
-                featureFlags,
+                variableDateViewControllerFactory,
                 batteryMeterViewController,
-                dumpManager
+                dumpManager,
+                featureFlags,
+                qsCarrierGroupControllerBuilder,
+                combinedShadeHeadersConstraintManager
         )
+        whenever(view.isAttachedToWindow).thenReturn(true)
+        mLargeScreenShadeHeaderController.init()
         carrierIconSlots = listOf(
                 context.getString(com.android.internal.R.string.status_bar_mobile))
     }
 
+    @After
+    fun verifyEveryTest() {
+        verifyZeroInteractions(combinedShadeHeadersConstraintManager)
+    }
+
     @Test
     fun setVisible_onlyWhenActive() {
         makeShadeVisible()
         assertThat(viewVisibility).isEqualTo(View.VISIBLE)
 
-        mLargeScreenShadeHeaderController.active = false
+        mLargeScreenShadeHeaderController.largeScreenActive = false
         assertThat(viewVisibility).isEqualTo(View.GONE)
     }
 
@@ -156,41 +175,16 @@
     }
 
     private fun makeShadeVisible() {
-        mLargeScreenShadeHeaderController.active = true
-        mLargeScreenShadeHeaderController.shadeExpanded = true
+        mLargeScreenShadeHeaderController.largeScreenActive = true
+        mLargeScreenShadeHeaderController.qsVisible = true
     }
 
     @Test
-    fun updateConfig_changesFontSize() {
-        val updatedTextPixelSize = 32
-        setReturnTextSize(updatedTextPixelSize)
-
+    fun updateConfig_changesFontStyle() {
         configurationController.notifyDensityOrFontScaleChanged()
 
-        verify(clock).setTextSize(COMPLEX_UNIT_PX, updatedTextPixelSize.toFloat())
-        verify(date).setTextSize(COMPLEX_UNIT_PX, updatedTextPixelSize.toFloat())
-        verify(carrierGroup).updateTextAppearance(R.style.TextAppearance_QS_Status)
-    }
-
-    @Test
-    fun updateConfig_changesFontSizeMultipleTimes() {
-        val updatedTextPixelSize1 = 32
-        setReturnTextSize(updatedTextPixelSize1)
-        configurationController.notifyDensityOrFontScaleChanged()
-        verify(clock).setTextSize(COMPLEX_UNIT_PX, updatedTextPixelSize1.toFloat())
-        verify(date).setTextSize(COMPLEX_UNIT_PX, updatedTextPixelSize1.toFloat())
-        verify(carrierGroup).updateTextAppearance(R.style.TextAppearance_QS_Status)
-        clearInvocations(carrierGroup)
-
-        val updatedTextPixelSize2 = 42
-        setReturnTextSize(updatedTextPixelSize2)
-        configurationController.notifyDensityOrFontScaleChanged()
-        verify(clock).setTextSize(COMPLEX_UNIT_PX, updatedTextPixelSize2.toFloat())
-        verify(date).setTextSize(COMPLEX_UNIT_PX, updatedTextPixelSize2.toFloat())
-        verify(carrierGroup).updateTextAppearance(R.style.TextAppearance_QS_Status)
-    }
-
-    private fun setReturnTextSize(resultTextSize: Int) {
-        whenever(typedArray.getDimensionPixelSize(anyInt(), anyInt())).thenReturn(resultTextSize)
+        verify(clock).setTextAppearance(R.style.TextAppearance_QS_Status)
+        verify(date).setTextAppearance(R.style.TextAppearance_QS_Status)
+        verify(carrierGroup).updateTextAppearance(R.style.TextAppearance_QS_Status_Carriers)
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java
index 5abcff3..e2673bb 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java
@@ -143,10 +143,8 @@
 import com.android.systemui.statusbar.phone.KeyguardBypassController;
 import com.android.systemui.statusbar.phone.KeyguardStatusBarView;
 import com.android.systemui.statusbar.phone.KeyguardStatusBarViewController;
-import com.android.systemui.statusbar.phone.LargeScreenShadeHeaderController;
 import com.android.systemui.statusbar.phone.LockscreenGestureLogger;
 import com.android.systemui.statusbar.phone.NotificationIconAreaController;
-import com.android.systemui.statusbar.phone.PanelViewController;
 import com.android.systemui.statusbar.phone.ScreenOffAnimationController;
 import com.android.systemui.statusbar.phone.ScrimController;
 import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
@@ -1182,11 +1180,11 @@
         mStatusBarStateController.setState(SHADE);
         when(mResources.getBoolean(R.bool.config_use_large_screen_shade_header)).thenReturn(true);
         mNotificationPanelViewController.updateResources();
-        verify(mLargeScreenShadeHeaderController).setActive(true);
+        verify(mLargeScreenShadeHeaderController).setLargeScreenActive(true);
 
         when(mResources.getBoolean(R.bool.config_use_large_screen_shade_header)).thenReturn(false);
         mNotificationPanelViewController.updateResources();
-        verify(mLargeScreenShadeHeaderController).setActive(false);
+        verify(mLargeScreenShadeHeaderController).setLargeScreenActive(false);
     }
 
     @Test
@@ -1286,6 +1284,29 @@
     }
 
     @Test
+    public void testPanelClosedWhenClosingQsInSplitShade() {
+        mPanelExpansionStateManager.onPanelExpansionChanged(/* fraction= */ 1,
+                /* expanded= */ true, /* tracking= */ false, /* dragDownPxAmount= */ 0);
+        enableSplitShade(/* enabled= */ true);
+        mNotificationPanelViewController.setExpandedFraction(1f);
+
+        assertThat(mNotificationPanelViewController.isClosing()).isFalse();
+        mNotificationPanelViewController.animateCloseQs(false);
+        assertThat(mNotificationPanelViewController.isClosing()).isTrue();
+    }
+
+    @Test
+    public void testPanelStaysOpenWhenClosingQs() {
+        mPanelExpansionStateManager.onPanelExpansionChanged(/* fraction= */ 1,
+                /* expanded= */ true, /* tracking= */ false, /* dragDownPxAmount= */ 0);
+        mNotificationPanelViewController.setExpandedFraction(1f);
+
+        assertThat(mNotificationPanelViewController.isClosing()).isFalse();
+        mNotificationPanelViewController.animateCloseQs(false);
+        assertThat(mNotificationPanelViewController.isClosing()).isFalse();
+    }
+
+    @Test
     public void interceptTouchEvent_withinQs_shadeExpanded_startsQsTracking() {
         mNotificationPanelViewController.mQs = mQs;
         when(mQsFrame.getX()).thenReturn(0f);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImplTest.java
index 72d3c2e..5386171 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImplTest.java
@@ -27,6 +27,7 @@
 import static com.android.systemui.statusbar.NotificationEntryHelper.modifyRanking;
 import static com.android.systemui.statusbar.StatusBarState.KEYGUARD;
 import static com.android.systemui.statusbar.StatusBarState.SHADE;
+import static com.android.systemui.statusbar.StatusBarState.SHADE_LOCKED;
 
 import static com.google.common.truth.Truth.assertThat;
 
@@ -61,6 +62,7 @@
 import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
 import com.android.systemui.statusbar.policy.BatteryController;
 import com.android.systemui.statusbar.policy.HeadsUpManager;
+import com.android.systemui.statusbar.policy.KeyguardStateController;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -87,6 +89,8 @@
     @Mock
     StatusBarStateController mStatusBarStateController;
     @Mock
+    KeyguardStateController mKeyguardStateController;
+    @Mock
     HeadsUpManager mHeadsUpManager;
     @Mock
     NotificationInterruptLogger mLogger;
@@ -106,6 +110,7 @@
     @Before
     public void setup() {
         MockitoAnnotations.initMocks(this);
+        when(mFlags.fullScreenIntentRequiresKeyguard()).thenReturn(false);
 
         mNotifInterruptionStateProvider =
                 new NotificationInterruptStateProviderImpl(
@@ -116,6 +121,7 @@
                         mNotificationFilter,
                         mBatteryController,
                         mStatusBarStateController,
+                        mKeyguardStateController,
                         mHeadsUpManager,
                         mLogger,
                         mMockHandler,
@@ -427,6 +433,12 @@
     }
 
     @Test
+    public void testShouldNotFullScreen_notPendingIntent_withStrictFlag() throws Exception {
+        when(mFlags.fullScreenIntentRequiresKeyguard()).thenReturn(true);
+        testShouldNotFullScreen_notPendingIntent();
+    }
+
+    @Test
     public void testShouldNotFullScreen_notPendingIntent() throws RemoteException {
         NotificationEntry entry = createNotification(IMPORTANCE_HIGH);
         when(mPowerManager.isInteractive()).thenReturn(true);
@@ -441,6 +453,12 @@
     }
 
     @Test
+    public void testShouldNotFullScreen_notHighImportance_withStrictFlag() throws Exception {
+        when(mFlags.fullScreenIntentRequiresKeyguard()).thenReturn(true);
+        testShouldNotFullScreen_notHighImportance();
+    }
+
+    @Test
     public void testShouldNotFullScreen_notHighImportance() throws RemoteException {
         NotificationEntry entry = createFsiNotification(IMPORTANCE_DEFAULT, /* silenced */ false);
         when(mPowerManager.isInteractive()).thenReturn(true);
@@ -455,6 +473,12 @@
     }
 
     @Test
+    public void testShouldNotFullScreen_isGroupAlertSilenced_withStrictFlag() throws Exception {
+        when(mFlags.fullScreenIntentRequiresKeyguard()).thenReturn(true);
+        testShouldNotFullScreen_isGroupAlertSilenced();
+    }
+
+    @Test
     public void testShouldNotFullScreen_isGroupAlertSilenced() throws RemoteException {
         NotificationEntry entry = createFsiNotification(IMPORTANCE_HIGH, /* silenced */ true);
         when(mPowerManager.isInteractive()).thenReturn(false);
@@ -469,6 +493,12 @@
     }
 
     @Test
+    public void testShouldFullScreen_notInteractive_withStrictFlag() throws Exception {
+        when(mFlags.fullScreenIntentRequiresKeyguard()).thenReturn(true);
+        testShouldFullScreen_notInteractive();
+    }
+
+    @Test
     public void testShouldFullScreen_notInteractive() throws RemoteException {
         NotificationEntry entry = createFsiNotification(IMPORTANCE_HIGH, /* silenced */ false);
         when(mPowerManager.isInteractive()).thenReturn(false);
@@ -483,6 +513,12 @@
     }
 
     @Test
+    public void testShouldFullScreen_isDreaming_withStrictFlag() throws Exception {
+        when(mFlags.fullScreenIntentRequiresKeyguard()).thenReturn(true);
+        testShouldFullScreen_isDreaming();
+    }
+
+    @Test
     public void testShouldFullScreen_isDreaming() throws RemoteException {
         NotificationEntry entry = createFsiNotification(IMPORTANCE_HIGH, /* silenced */ false);
         when(mPowerManager.isInteractive()).thenReturn(true);
@@ -497,6 +533,12 @@
     }
 
     @Test
+    public void testShouldFullScreen_onKeyguard_withStrictFlag() throws Exception {
+        when(mFlags.fullScreenIntentRequiresKeyguard()).thenReturn(true);
+        testShouldFullScreen_onKeyguard();
+    }
+
+    @Test
     public void testShouldFullScreen_onKeyguard() throws RemoteException {
         NotificationEntry entry = createFsiNotification(IMPORTANCE_HIGH, /* silenced */ false);
         when(mPowerManager.isInteractive()).thenReturn(true);
@@ -511,6 +553,12 @@
     }
 
     @Test
+    public void testShouldNotFullScreen_willHun_withStrictFlag() throws Exception {
+        when(mFlags.fullScreenIntentRequiresKeyguard()).thenReturn(true);
+        testShouldNotFullScreen_willHun();
+    }
+
+    @Test
     public void testShouldNotFullScreen_willHun() throws RemoteException {
         NotificationEntry entry = createFsiNotification(IMPORTANCE_HIGH, /* silenced */ false);
         when(mPowerManager.isInteractive()).thenReturn(true);
@@ -542,6 +590,66 @@
         verify(mLogger).logFullscreen(entry, "Expected not to HUN");
     }
 
+    @Test
+    public void testShouldFullScreen_snoozed_occluding_withStrictRules() throws Exception {
+        when(mFlags.fullScreenIntentRequiresKeyguard()).thenReturn(true);
+        NotificationEntry entry = createFsiNotification(IMPORTANCE_HIGH, /* silenced */ false);
+        when(mPowerManager.isInteractive()).thenReturn(true);
+        when(mPowerManager.isScreenOn()).thenReturn(true);
+        when(mDreamManager.isDreaming()).thenReturn(false);
+        when(mStatusBarStateController.getState()).thenReturn(SHADE);
+        when(mHeadsUpManager.isSnoozed("a")).thenReturn(true);
+        when(mKeyguardStateController.isShowing()).thenReturn(true);
+        when(mKeyguardStateController.isOccluded()).thenReturn(true);
+
+        assertThat(mNotifInterruptionStateProvider.shouldLaunchFullScreenIntentWhenAdded(entry))
+                .isTrue();
+        verify(mLogger).logNoHeadsUpPackageSnoozed(entry);
+        verify(mLogger, never()).logNoFullscreen(any(), any());
+        verify(mLogger, never()).logNoFullscreenWarning(any(), any());
+        verify(mLogger).logFullscreen(entry, "Expected not to HUN while keyguard occluded");
+    }
+
+    @Test
+    public void testShouldFullScreen_snoozed_lockedShade_withStrictRules() throws Exception {
+        when(mFlags.fullScreenIntentRequiresKeyguard()).thenReturn(true);
+        NotificationEntry entry = createFsiNotification(IMPORTANCE_HIGH, /* silenced */ false);
+        when(mPowerManager.isInteractive()).thenReturn(true);
+        when(mPowerManager.isScreenOn()).thenReturn(true);
+        when(mDreamManager.isDreaming()).thenReturn(false);
+        when(mStatusBarStateController.getState()).thenReturn(SHADE_LOCKED);
+        when(mHeadsUpManager.isSnoozed("a")).thenReturn(true);
+        when(mKeyguardStateController.isShowing()).thenReturn(true);
+        when(mKeyguardStateController.isOccluded()).thenReturn(false);
+
+        assertThat(mNotifInterruptionStateProvider.shouldLaunchFullScreenIntentWhenAdded(entry))
+                .isTrue();
+        verify(mLogger).logNoHeadsUpPackageSnoozed(entry);
+        verify(mLogger, never()).logNoFullscreen(any(), any());
+        verify(mLogger, never()).logNoFullscreenWarning(any(), any());
+        verify(mLogger).logFullscreen(entry, "Keyguard is showing and not occluded");
+    }
+
+    @Test
+    public void testShouldNotFullScreen_snoozed_unlocked_withStrictRules() throws Exception {
+        when(mFlags.fullScreenIntentRequiresKeyguard()).thenReturn(true);
+        NotificationEntry entry = createFsiNotification(IMPORTANCE_HIGH, /* silenced */ false);
+        when(mPowerManager.isInteractive()).thenReturn(true);
+        when(mPowerManager.isScreenOn()).thenReturn(true);
+        when(mDreamManager.isDreaming()).thenReturn(false);
+        when(mStatusBarStateController.getState()).thenReturn(SHADE);
+        when(mHeadsUpManager.isSnoozed("a")).thenReturn(true);
+        when(mKeyguardStateController.isShowing()).thenReturn(false);
+        when(mKeyguardStateController.isOccluded()).thenReturn(false);
+
+        assertThat(mNotifInterruptionStateProvider.shouldLaunchFullScreenIntentWhenAdded(entry))
+                .isFalse();
+        verify(mLogger).logNoHeadsUpPackageSnoozed(entry);
+        verify(mLogger, never()).logNoFullscreen(any(), any());
+        verify(mLogger).logNoFullscreenWarning(entry, "Expected not to HUN while not on keyguard");
+        verify(mLogger, never()).logFullscreen(any(), any());
+    }
+
     /**
      * Bubbles can happen.
      */
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ChannelEditorDialogControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ChannelEditorDialogControllerTest.kt
index 64d0256..214ba16 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ChannelEditorDialogControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ChannelEditorDialogControllerTest.kt
@@ -98,7 +98,7 @@
 
     @Test
     fun testPrepareDialogForApp_onlyDefaultChannel() {
-        group.addChannel(channelDefault)
+        group.channels = listOf(channelDefault)
 
         controller.prepareDialogForApp(TEST_APP_NAME, TEST_PACKAGE_NAME, TEST_UID,
                 setOf(channelDefault), appIcon, clickListener)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java
index b75c52a..c6fb0ce 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java
@@ -306,8 +306,13 @@
         mNotificationInterruptStateProvider =
                 new TestableNotificationInterruptStateProviderImpl(mContext.getContentResolver(),
                         mPowerManager,
-                        mDreamManager, mAmbientDisplayConfiguration, mNotificationFilter,
-                        mStatusBarStateController, mBatteryController, mHeadsUpManager,
+                        mDreamManager,
+                        mAmbientDisplayConfiguration,
+                        mNotificationFilter,
+                        mStatusBarStateController,
+                        mKeyguardStateController,
+                        mBatteryController,
+                        mHeadsUpManager,
                         mock(NotificationInterruptLogger.class),
                         new Handler(TestableLooper.get(this).getLooper()),
                         mock(NotifPipelineFlags.class),
@@ -1036,15 +1041,28 @@
                 AmbientDisplayConfiguration ambientDisplayConfiguration,
                 NotificationFilter filter,
                 StatusBarStateController controller,
+                KeyguardStateController keyguardStateController,
                 BatteryController batteryController,
                 HeadsUpManager headsUpManager,
                 NotificationInterruptLogger logger,
                 Handler mainHandler,
                 NotifPipelineFlags flags,
                 KeyguardNotificationVisibilityProvider keyguardNotificationVisibilityProvider) {
-            super(contentResolver, powerManager, dreamManager, ambientDisplayConfiguration, filter,
-                    batteryController, controller, headsUpManager, logger, mainHandler,
-                    flags, keyguardNotificationVisibilityProvider);
+            super(
+                    contentResolver,
+                    powerManager,
+                    dreamManager,
+                    ambientDisplayConfiguration,
+                    filter,
+                    batteryController,
+                    controller,
+                    keyguardStateController,
+                    headsUpManager,
+                    logger,
+                    mainHandler,
+                    flags,
+                    keyguardNotificationVisibilityProvider
+            );
             mUseHeadsUp = true;
         }
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt
index ba29e95..9892448 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt
@@ -26,6 +26,7 @@
 import androidx.test.platform.app.InstrumentationRegistry
 import com.android.systemui.R
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.shade.PanelViewController
 import com.android.systemui.statusbar.phone.userswitcher.StatusBarUserSwitcherController
 import com.android.systemui.statusbar.policy.ConfigurationController
 import com.android.systemui.unfold.SysUIUnfoldComponent
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewTest.kt
index 8d686ae..d6c995b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewTest.kt
@@ -20,6 +20,7 @@
 import android.view.ViewGroup
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.shade.PanelViewController
 import com.google.common.truth.Truth.assertThat
 import org.junit.Before
 import org.junit.Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarBoundsProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarBoundsProviderTest.kt
new file mode 100644
index 0000000..d84010d
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarBoundsProviderTest.kt
@@ -0,0 +1,171 @@
+/*
+ * 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.phone
+
+import android.graphics.Rect
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper.RunWithLooper
+import android.view.View
+import android.widget.FrameLayout
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.statusbar.phone.StatusBarBoundsProvider.BoundsChangeListener
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito.any
+import org.mockito.Mockito.doAnswer
+import org.mockito.Mockito.never
+import org.mockito.Mockito.spy
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+@RunWith(AndroidTestingRunner::class)
+@RunWithLooper(setAsMainLooper = true)
+@SmallTest
+class StatusBarBoundsProviderTest : SysuiTestCase() {
+
+    companion object {
+        private val START_SIDE_BOUNDS = Rect(50, 100, 150, 200)
+        private val END_SIDE_BOUNDS = Rect(250, 300, 350, 400)
+    }
+
+    @Mock private lateinit var boundsChangeListener: BoundsChangeListener
+
+    private lateinit var boundsProvider: StatusBarBoundsProvider
+
+    private lateinit var startSideContent: View
+    private lateinit var endSideContent: View
+
+    @Before
+    fun setUp() {
+        MockitoAnnotations.initMocks(this)
+
+        startSideContent = spy(FrameLayout(context)).apply { setBoundsOnScreen(START_SIDE_BOUNDS) }
+        endSideContent = spy(FrameLayout(context)).apply { setBoundsOnScreen(END_SIDE_BOUNDS) }
+
+        boundsProvider =
+            StatusBarBoundsProvider(setOf(boundsChangeListener), startSideContent, endSideContent)
+    }
+
+    @Test
+    fun visibleStartSideBounds_returnsBoundsFromStartSideContentView() {
+        assertThat(boundsProvider.visibleStartSideBounds).isEqualTo(START_SIDE_BOUNDS)
+    }
+
+    @Test
+    fun visibleEndSideBounds_returnsBoundsFromEndSideContentView() {
+        assertThat(boundsProvider.visibleEndSideBounds).isEqualTo(END_SIDE_BOUNDS)
+    }
+
+    @Test
+    fun startBoundsChange_afterStart_notifiesListener() {
+        boundsProvider.start()
+        val newBounds = Rect(START_SIDE_BOUNDS).apply { left += 1 }
+
+        startSideContent.setBoundsOnScreen(newBounds)
+
+        verify(boundsChangeListener).onStatusBarBoundsChanged()
+    }
+
+    @Test
+    fun startBoundsChange_beforeStart_doesNotNotifyListener() {
+        val newBounds = Rect(START_SIDE_BOUNDS).apply { left += 1 }
+
+        startSideContent.setBoundsOnScreen(newBounds)
+
+        verify(boundsChangeListener, never()).onStatusBarBoundsChanged()
+    }
+
+    @Test
+    fun startBoundsChange_afterStop_doesNotNotifyListener() {
+        boundsProvider.start()
+        boundsProvider.stop()
+        val newBounds = Rect(START_SIDE_BOUNDS).apply { left += 1 }
+
+        startSideContent.setBoundsOnScreen(newBounds)
+
+        verify(boundsChangeListener, never()).onStatusBarBoundsChanged()
+    }
+
+    @Test
+    fun startLayoutChange_afterStart_boundsOnScreenSame_doesNotNotifyListener() {
+        boundsProvider.start()
+        val newBounds = Rect(START_SIDE_BOUNDS).apply { left += 1 }
+
+        startSideContent.layout(newBounds)
+
+        verify(boundsChangeListener, never()).onStatusBarBoundsChanged()
+    }
+
+    @Test
+    fun endBoundsChange_afterStart_notifiesListener() {
+        boundsProvider.start()
+        val newBounds = Rect(START_SIDE_BOUNDS).apply { right += 1 }
+
+        endSideContent.setBoundsOnScreen(newBounds)
+
+        verify(boundsChangeListener).onStatusBarBoundsChanged()
+    }
+
+    @Test
+    fun endBoundsChange_beforeStart_doesNotNotifyListener() {
+        val newBounds = Rect(START_SIDE_BOUNDS).apply { right += 1 }
+
+        endSideContent.setBoundsOnScreen(newBounds)
+
+        verify(boundsChangeListener, never()).onStatusBarBoundsChanged()
+    }
+
+    @Test
+    fun endBoundsChange_afterStop_doesNotNotifyListener() {
+        boundsProvider.start()
+        boundsProvider.stop()
+        val newBounds = Rect(START_SIDE_BOUNDS).apply { right += 1 }
+
+        endSideContent.setBoundsOnScreen(newBounds)
+
+        verify(boundsChangeListener, never()).onStatusBarBoundsChanged()
+    }
+
+    @Test
+    fun endLayoutChange_afterStart_boundsOnScreenSame_doesNotNotifyListener() {
+        boundsProvider.start()
+        val newBounds = Rect(START_SIDE_BOUNDS).apply { right += 1 }
+
+        endSideContent.layout(newBounds)
+
+        verify(boundsChangeListener, never()).onStatusBarBoundsChanged()
+    }
+}
+
+private fun View.setBoundsOnScreen(bounds: Rect) {
+    doAnswer { invocation ->
+            val boundsOutput = invocation.arguments[0] as Rect
+            boundsOutput.set(bounds)
+            return@doAnswer Unit
+        }
+        .`when`(this)
+        .getBoundsOnScreen(any())
+    layout(bounds.left, bounds.top, bounds.right, bounds.bottom)
+}
+
+private fun View.layout(rect: Rect) {
+    layout(rect.left, rect.top, rect.right, rect.bottom)
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenterTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenterTest.java
index 4b5d1f7..fdb2977 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenterTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenterTest.java
@@ -16,12 +16,14 @@
 
 import static android.view.Display.DEFAULT_DISPLAY;
 
+import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
 import android.app.Notification;
+import android.app.PendingIntent;
 import android.app.StatusBarManager;
 import android.metrics.LogMaker;
 import android.support.test.metricshelper.MetricsAsserts;
@@ -80,6 +82,8 @@
     private FakeMetricsLogger mMetricsLogger;
     private ShadeController mShadeController = mock(ShadeController.class);
     private CentralSurfaces mCentralSurfaces = mock(CentralSurfaces.class);
+    private KeyguardStateController mKeyguardStateController = mock(KeyguardStateController.class);
+    private NotifPipelineFlags mNotifPipelineFlags = mock(NotifPipelineFlags.class);
     private InitController mInitController = new InitController();
 
     @Before
@@ -114,7 +118,7 @@
                 mock(ScrimController.class),
                 mock(NotificationShadeWindowController.class),
                 mock(DynamicPrivacyController.class),
-                mock(KeyguardStateController.class),
+                mKeyguardStateController,
                 mock(KeyguardIndicationController.class),
                 mCentralSurfaces,
                 mock(ShadeControllerImpl.class),
@@ -130,7 +134,7 @@
                 mInitController,
                 mNotificationInterruptStateProvider,
                 mock(NotificationRemoteInputManager.class),
-                mock(NotifPipelineFlags.class),
+                mNotifPipelineFlags,
                 mock(NotificationRemoteInputManager.Callback.class),
                 mock(NotificationListContainer.class));
         mInitController.executePostInitTasks();
@@ -141,6 +145,19 @@
     }
 
     @Test
+    public void testNoSuppressHeadsUp_default() {
+        Notification n = new Notification.Builder(getContext(), "a").build();
+        NotificationEntry entry = new NotificationEntryBuilder()
+                .setPkg("a")
+                .setOpPkg("a")
+                .setTag("a")
+                .setNotification(n)
+                .build();
+
+        assertFalse(mInterruptSuppressor.suppressAwakeHeadsUp(entry));
+    }
+
+    @Test
     public void testSuppressHeadsUp_disabledStatusBar() {
         Notification n = new Notification.Builder(getContext(), "a").build();
         NotificationEntry entry = new NotificationEntryBuilder()
@@ -176,6 +193,63 @@
     }
 
     @Test
+    public void testNoSuppressHeadsUp_FSI_occludedKeygaurd() {
+        when(mNotifPipelineFlags.fullScreenIntentRequiresKeyguard()).thenReturn(false);
+        Notification n = new Notification.Builder(getContext(), "a")
+                .setFullScreenIntent(mock(PendingIntent.class), true)
+                .build();
+        NotificationEntry entry = new NotificationEntryBuilder()
+                .setPkg("a")
+                .setOpPkg("a")
+                .setTag("a")
+                .setNotification(n)
+                .build();
+
+        when(mKeyguardStateController.isShowing()).thenReturn(true);
+        when(mKeyguardStateController.isOccluded()).thenReturn(true);
+        when(mCentralSurfaces.isOccluded()).thenReturn(true);
+        assertFalse(mInterruptSuppressor.suppressAwakeHeadsUp(entry));
+    }
+
+    @Test
+    public void testSuppressHeadsUp_FSI_nonOccludedKeygaurd() {
+        when(mNotifPipelineFlags.fullScreenIntentRequiresKeyguard()).thenReturn(false);
+        Notification n = new Notification.Builder(getContext(), "a")
+                .setFullScreenIntent(mock(PendingIntent.class), true)
+                .build();
+        NotificationEntry entry = new NotificationEntryBuilder()
+                .setPkg("a")
+                .setOpPkg("a")
+                .setTag("a")
+                .setNotification(n)
+                .build();
+
+        when(mKeyguardStateController.isShowing()).thenReturn(true);
+        when(mKeyguardStateController.isOccluded()).thenReturn(false);
+        when(mCentralSurfaces.isOccluded()).thenReturn(false);
+        assertTrue(mInterruptSuppressor.suppressAwakeHeadsUp(entry));
+    }
+
+    @Test
+    public void testNoSuppressHeadsUp_FSI_nonOccludedKeygaurd_withNewFlag() {
+        when(mNotifPipelineFlags.fullScreenIntentRequiresKeyguard()).thenReturn(true);
+        Notification n = new Notification.Builder(getContext(), "a")
+                .setFullScreenIntent(mock(PendingIntent.class), true)
+                .build();
+        NotificationEntry entry = new NotificationEntryBuilder()
+                .setPkg("a")
+                .setOpPkg("a")
+                .setTag("a")
+                .setNotification(n)
+                .build();
+
+        when(mKeyguardStateController.isShowing()).thenReturn(true);
+        when(mKeyguardStateController.isOccluded()).thenReturn(false);
+        when(mCentralSurfaces.isOccluded()).thenReturn(false);
+        assertFalse(mInterruptSuppressor.suppressAwakeHeadsUp(entry));
+    }
+
+    @Test
     public void testSuppressInterruptions_vrMode() {
         Notification n = new Notification.Builder(getContext(), "a").build();
         NotificationEntry entry = new NotificationEntryBuilder()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java
index 681e998..56804d5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java
@@ -41,6 +41,7 @@
 
 import com.android.systemui.R;
 import com.android.systemui.SysuiBaseFragmentTest;
+import com.android.systemui.dump.DumpManager;
 import com.android.systemui.flags.FeatureFlags;
 import com.android.systemui.log.LogBuffer;
 import com.android.systemui.log.LogcatEchoTracker;
@@ -107,6 +108,8 @@
     private NotificationPanelViewController mNotificationPanelViewController;
     @Mock
     private StatusBarHideIconsForBouncerManager mStatusBarHideIconsForBouncerManager;
+    @Mock
+    private DumpManager mDumpManager;
 
     public CollapsedStatusBarFragmentTest() {
         super(CollapsedStatusBarFragment.class);
@@ -386,7 +389,8 @@
                         ),
                 mOperatorNameViewControllerFactory,
                 mSecureSettings,
-                mExecutor);
+                mExecutor,
+                mDumpManager);
     }
 
     private void setUpDaggerComponent() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
index 6a0124a..9866013 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
@@ -321,6 +321,7 @@
                         mock(AmbientDisplayConfiguration.class),
                         mock(NotificationFilter.class),
                         mock(StatusBarStateController.class),
+                        mock(KeyguardStateController.class),
                         mock(BatteryController.class),
                         mock(HeadsUpManager.class),
                         mock(NotificationInterruptLogger.class),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/TestableNotificationInterruptStateProviderImpl.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/TestableNotificationInterruptStateProviderImpl.java
index a7f0dc2..d80ea15 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/TestableNotificationInterruptStateProviderImpl.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/TestableNotificationInterruptStateProviderImpl.java
@@ -30,6 +30,7 @@
 import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProviderImpl;
 import com.android.systemui.statusbar.policy.BatteryController;
 import com.android.systemui.statusbar.policy.HeadsUpManager;
+import com.android.systemui.statusbar.policy.KeyguardStateController;
 
 public class TestableNotificationInterruptStateProviderImpl
         extends NotificationInterruptStateProviderImpl {
@@ -41,6 +42,7 @@
             AmbientDisplayConfiguration ambientDisplayConfiguration,
             NotificationFilter filter,
             StatusBarStateController statusBarStateController,
+            KeyguardStateController keyguardStateController,
             BatteryController batteryController,
             HeadsUpManager headsUpManager,
             NotificationInterruptLogger logger,
@@ -54,6 +56,7 @@
                 filter,
                 batteryController,
                 statusBarStateController,
+                keyguardStateController,
                 headsUpManager,
                 logger,
                 mainHandler,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/WMShellTest.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/WMShellTest.java
index 72ade26..9c21366 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/WMShellTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/WMShellTest.java
@@ -33,7 +33,6 @@
 import com.android.systemui.statusbar.policy.KeyguardStateController;
 import com.android.systemui.statusbar.policy.UserInfoController;
 import com.android.systemui.tracing.ProtoTracer;
-import com.android.wm.shell.ShellCommandHandler;
 import com.android.wm.shell.common.ShellExecutor;
 import com.android.wm.shell.onehanded.OneHanded;
 import com.android.wm.shell.onehanded.OneHandedEventCallback;
@@ -73,7 +72,6 @@
     @Mock OneHanded mOneHanded;
     @Mock WakefulnessLifecycle mWakefulnessLifecycle;
     @Mock ProtoTracer mProtoTracer;
-    @Mock ShellCommandHandler mShellCommandHandler;
     @Mock UserInfoController mUserInfoController;
     @Mock ShellExecutor mSysUiMainExecutor;
 
@@ -82,10 +80,10 @@
         MockitoAnnotations.initMocks(this);
 
         mWMShell = new WMShell(mContext, mShellInterface, Optional.of(mPip),
-                Optional.of(mSplitScreen), Optional.of(mOneHanded),
-                Optional.of(mShellCommandHandler), mCommandQueue, mConfigurationController,
-                mKeyguardStateController, mKeyguardUpdateMonitor, mScreenLifecycle, mSysUiState,
-                mProtoTracer, mWakefulnessLifecycle, mUserInfoController, mSysUiMainExecutor);
+                Optional.of(mSplitScreen), Optional.of(mOneHanded), mCommandQueue,
+                mConfigurationController, mKeyguardStateController, mKeyguardUpdateMonitor,
+                mScreenLifecycle, mSysUiState, mProtoTracer, mWakefulnessLifecycle,
+                mUserInfoController, mSysUiMainExecutor);
     }
 
     @Test
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index 0040ea9..3a869f8 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -9181,11 +9181,8 @@
                 Settings.Secure.SPATIAL_AUDIO_ENABLED, UserHandle.USER_CURRENT);
         if (settings == null) {
             Log.e(TAG, "error reading spatial audio device settings");
-        } else {
-            Log.v(TAG, "restoring spatial audio device settings: " + settings);
-            mSpatializerHelper.setSADeviceSettings(settings);
         }
-        mSpatializerHelper.init(/*effectExpected*/ mHasSpatializerEffect);
+        mSpatializerHelper.init(/*effectExpected*/ mHasSpatializerEffect, settings);
         mSpatializerHelper.setFeatureEnabled(mHasSpatializerEffect);
     }
 
@@ -10373,6 +10370,11 @@
 
         @Override
         public void setAccessibilityServiceUids(IntArray uids) {
+            // TODO(b/233287010): Fix voice interaction and a11y concurrency in audio policy service
+            if (isPlatformAutomotive()) {
+                return;
+            }
+
             synchronized (mAccessibilityServiceUidsLock) {
                 if (uids.size() == 0) {
                     mAccessibilityServiceUids = null;
diff --git a/services/core/java/com/android/server/audio/SpatializerHelper.java b/services/core/java/com/android/server/audio/SpatializerHelper.java
index dd44af1..cd5960f 100644
--- a/services/core/java/com/android/server/audio/SpatializerHelper.java
+++ b/services/core/java/com/android/server/audio/SpatializerHelper.java
@@ -175,7 +175,7 @@
         mASA = asa;
     }
 
-    synchronized void init(boolean effectExpected) {
+    synchronized void init(boolean effectExpected, @Nullable String settings) {
         loglogi("init effectExpected=" + effectExpected);
         if (!effectExpected) {
             loglogi("init(): setting state to STATE_NOT_SUPPORTED due to effect not expected");
@@ -278,6 +278,13 @@
                     mSACapableDeviceTypes.add(SPAT_MODE_FOR_DEVICE_TYPE.keyAt(i));
                 }
             }
+
+            // When initialized from AudioService, the settings string will be non-null.
+            // Saved settings need to be applied after spatialization support is initialized above.
+            if (settings != null) {
+                setSADeviceSettings(settings);
+            }
+
             // for both transaural / binaural, we are not forcing enablement as the init() method
             // could have been called another time after boot in case of audioserver restart
             addCompatibleAudioDevice(
@@ -316,7 +323,7 @@
         mState = STATE_UNINITIALIZED;
         mSpatLevel = Spatializer.SPATIALIZER_IMMERSIVE_LEVEL_NONE;
         mActualHeadTrackingMode = Spatializer.HEAD_TRACKING_MODE_UNSUPPORTED;
-        init(true);
+        init(true, null /* settings */);
         setSpatializerEnabledInt(featureEnabled);
     }
 
@@ -731,7 +738,7 @@
                 return;
             }
             if (mState == STATE_UNINITIALIZED) {
-                init(true);
+                init(true, null /* settings */);
             }
             setSpatializerEnabledInt(true);
         } else {
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/UsageStats.java b/services/core/java/com/android/server/biometrics/sensors/face/UsageStats.java
index d99abcd..9494547 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/UsageStats.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/UsageStats.java
@@ -70,20 +70,24 @@
 
     private int mAcceptCount;
     private int mRejectCount;
-    private SparseIntArray mErrorCount;
+    private int mErrorCount;
+    private int mAuthAttemptCount;
+    private SparseIntArray mErrorFrequencyMap;
 
     private long mAcceptLatency;
     private long mRejectLatency;
-    private SparseLongArray mErrorLatency;
+    private long mErrorLatency;
+    private SparseLongArray mErrorLatencyMap;
 
     public UsageStats(Context context) {
         mAuthenticationEvents = new ArrayDeque<>();
-        mErrorCount = new SparseIntArray();
-        mErrorLatency = new SparseLongArray();
+        mErrorFrequencyMap = new SparseIntArray();
+        mErrorLatencyMap = new SparseLongArray();
         mContext = context;
     }
 
     public void addEvent(AuthenticationEvent event) {
+        mAuthAttemptCount++;
         if (mAuthenticationEvents.size() >= EVENT_LOG_SIZE) {
             mAuthenticationEvents.removeFirst();
         }
@@ -96,29 +100,38 @@
             mRejectCount++;
             mRejectLatency += event.mLatency;
         } else {
-            mErrorCount.put(event.mError, mErrorCount.get(event.mError, 0) + 1);
-            mErrorLatency.put(event.mError, mErrorLatency.get(event.mError, 0L) + event.mLatency);
+            mErrorCount++;
+            mErrorLatency += event.mLatency;
+            mErrorFrequencyMap.put(event.mError, mErrorFrequencyMap.get(event.mError, 0) + 1);
+            mErrorLatencyMap.put(event.mError,
+                    mErrorLatencyMap.get(event.mError, 0L) + event.mLatency);
         }
     }
 
     public void print(PrintWriter pw) {
-        pw.println("Events since last reboot: " + mAuthenticationEvents.size());
+        pw.println("Printing most recent events since last reboot("
+                + mAuthenticationEvents.size() + " events)");
         for (AuthenticationEvent event : mAuthenticationEvents) {
             pw.println(event.toString(mContext));
         }
 
         // Dump aggregated usage stats
-        pw.println("Accept\tCount: " + mAcceptCount + "\tLatency: " + mAcceptLatency
+        pw.println("");
+        pw.println("Accept Count: " + mAcceptCount + "\tLatency: " + mAcceptLatency
                 + "\tAverage: " + (mAcceptCount > 0 ? mAcceptLatency / mAcceptCount : 0));
-        pw.println("Reject\tCount: " + mRejectCount + "\tLatency: " + mRejectLatency
+        pw.println("Reject Count: " + mRejectCount + "\tLatency: " + mRejectLatency
                 + "\tAverage: " + (mRejectCount > 0 ? mRejectLatency / mRejectCount : 0));
+        pw.println("Total Error Count: " + mErrorCount + "\tLatency: " + mErrorLatency
+                + "\tAverage: " + (mErrorCount > 0 ? mErrorLatency / mErrorCount : 0));
+        pw.println("Total Attempts: " + mAuthAttemptCount);
+        pw.println("");
 
-        for (int i = 0; i < mErrorCount.size(); i++) {
-            final int key = mErrorCount.keyAt(i);
-            final int count = mErrorCount.get(i);
+        for (int i = 0; i < mErrorFrequencyMap.size(); i++) {
+            final int key = mErrorFrequencyMap.keyAt(i);
+            final int count = mErrorFrequencyMap.get(key);
             pw.println("Error" + key + "\tCount: " + count
-                    + "\tLatency: " + mErrorLatency.get(key, 0L)
-                    + "\tAverage: " + (count > 0 ? mErrorLatency.get(key, 0L) / count : 0)
+                    + "\tLatency: " + mErrorLatencyMap.get(key, 0L)
+                    + "\tAverage: " + (count > 0 ? mErrorLatencyMap.get(key, 0L) / count : 0)
                     + "\t" + FaceManager.getErrorString(mContext, key, 0 /* vendorCode */));
         }
     }
diff --git a/services/core/java/com/android/server/display/DisplayDeviceConfig.java b/services/core/java/com/android/server/display/DisplayDeviceConfig.java
index 2322280d..b5aa7b1 100644
--- a/services/core/java/com/android/server/display/DisplayDeviceConfig.java
+++ b/services/core/java/com/android/server/display/DisplayDeviceConfig.java
@@ -287,7 +287,8 @@
     private BrightnessThrottlingData mBrightnessThrottlingData;
     private BrightnessThrottlingData mOriginalBrightnessThrottlingData;
 
-    private DisplayDeviceConfig(Context context) {
+    @VisibleForTesting
+    DisplayDeviceConfig(Context context) {
         mContext = context;
     }
 
@@ -691,7 +692,8 @@
         return config;
     }
 
-    private boolean initFromFile(File configFile) {
+    @VisibleForTesting
+    boolean initFromFile(File configFile) {
         if (!configFile.exists()) {
             // Display configuration files aren't required to exist.
             return false;
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 9486a45..e589080 100755
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -2459,11 +2459,11 @@
         SnoozeHelper snoozeHelper = new SnoozeHelper(getContext(), (userId, r, muteOnReturn) -> {
             try {
                 if (DBG) {
-                    Slog.d(TAG, "Reposting " + r.getKey());
+                    Slog.d(TAG, "Reposting " + r.getKey() + " " + muteOnReturn);
                 }
                 enqueueNotificationInternal(r.getSbn().getPackageName(), r.getSbn().getOpPkg(),
                         r.getSbn().getUid(), r.getSbn().getInitialPid(), r.getSbn().getTag(),
-                        r.getSbn().getId(),  r.getSbn().getNotification(), userId, true);
+                        r.getSbn().getId(),  r.getSbn().getNotification(), userId, muteOnReturn);
             } catch (Exception e) {
                 Slog.e(TAG, "Cannot un-snooze notification", e);
             }
diff --git a/services/core/java/com/android/server/notification/PreferencesHelper.java b/services/core/java/com/android/server/notification/PreferencesHelper.java
index 477b8da..066692d 100644
--- a/services/core/java/com/android/server/notification/PreferencesHelper.java
+++ b/services/core/java/com/android/server/notification/PreferencesHelper.java
@@ -86,6 +86,7 @@
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collection;
+import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.Objects;
@@ -1327,16 +1328,17 @@
                 return null;
             }
             NotificationChannelGroup group = r.groups.get(groupId).clone();
-            group.setChannels(new ArrayList<>());
+            ArrayList channels = new ArrayList();
             int N = r.channels.size();
             for (int i = 0; i < N; i++) {
                 final NotificationChannel nc = r.channels.valueAt(i);
                 if (includeDeleted || !nc.isDeleted()) {
                     if (groupId.equals(nc.getGroup())) {
-                        group.addChannel(nc);
+                        channels.add(nc);
                     }
                 }
             }
+            group.setChannels(channels);
             return group;
         }
     }
@@ -1357,44 +1359,48 @@
     public ParceledListSlice<NotificationChannelGroup> getNotificationChannelGroups(String pkg,
             int uid, boolean includeDeleted, boolean includeNonGrouped, boolean includeEmpty) {
         Objects.requireNonNull(pkg);
-        Map<String, NotificationChannelGroup> groups = new ArrayMap<>();
+        List<NotificationChannelGroup> groups = new ArrayList<>();
         synchronized (mPackagePreferences) {
             PackagePreferences r = getPackagePreferencesLocked(pkg, uid);
             if (r == null) {
                 return ParceledListSlice.emptyList();
             }
-            NotificationChannelGroup nonGrouped = new NotificationChannelGroup(null, null);
+            Map<String, ArrayList<NotificationChannel>> groupedChannels = new HashMap();
             int N = r.channels.size();
             for (int i = 0; i < N; i++) {
                 final NotificationChannel nc = r.channels.valueAt(i);
                 if (includeDeleted || !nc.isDeleted()) {
                     if (nc.getGroup() != null) {
                         if (r.groups.get(nc.getGroup()) != null) {
-                            NotificationChannelGroup ncg = groups.get(nc.getGroup());
-                            if (ncg == null) {
-                                ncg = r.groups.get(nc.getGroup()).clone();
-                                ncg.setChannels(new ArrayList<>());
-                                groups.put(nc.getGroup(), ncg);
-
-                            }
-                            ncg.addChannel(nc);
+                            ArrayList<NotificationChannel> channels = groupedChannels.getOrDefault(
+                                    nc.getGroup(), new ArrayList<>());
+                            channels.add(nc);
+                            groupedChannels.put(nc.getGroup(), channels);
                         }
                     } else {
-                        nonGrouped.addChannel(nc);
+                        ArrayList<NotificationChannel> channels = groupedChannels.getOrDefault(
+                            null, new ArrayList<>());
+                        channels.add(nc);
+                        groupedChannels.put(null, channels);
                     }
                 }
             }
-            if (includeNonGrouped && nonGrouped.getChannels().size() > 0) {
-                groups.put(null, nonGrouped);
-            }
-            if (includeEmpty) {
-                for (NotificationChannelGroup group : r.groups.values()) {
-                    if (!groups.containsKey(group.getId())) {
-                        groups.put(group.getId(), group);
-                    }
+            for (NotificationChannelGroup group : r.groups.values()) {
+                ArrayList<NotificationChannel> channels =
+                        groupedChannels.getOrDefault(group.getId(), new ArrayList<>());
+                if (includeEmpty || !channels.isEmpty()) {
+                    NotificationChannelGroup clone = group.clone();
+                    clone.setChannels(channels);
+                    groups.add(clone);
                 }
             }
-            return new ParceledListSlice<>(new ArrayList<>(groups.values()));
+
+            if (includeNonGrouped && groupedChannels.containsKey(null)) {
+                NotificationChannelGroup nonGrouped = new NotificationChannelGroup(null, null);
+                nonGrouped.setChannels(groupedChannels.get(null));
+                groups.add(nonGrouped);
+            }
+            return new ParceledListSlice<>(groups);
         }
     }
 
diff --git a/services/core/java/com/android/server/pm/ScanPackageUtils.java b/services/core/java/com/android/server/pm/ScanPackageUtils.java
index 0dc188b..46f0dbc 100644
--- a/services/core/java/com/android/server/pm/ScanPackageUtils.java
+++ b/services/core/java/com/android/server/pm/ScanPackageUtils.java
@@ -427,8 +427,7 @@
         pkgSetting.setLastModifiedTime(scanFileTime);
         // TODO(b/135203078): Remove, move to constructor
         pkgSetting.setPkg(parsedPackage)
-                .setFlags(PackageInfoUtils.appInfoFlags(parsedPackage, pkgSetting))
-                .setPrivateFlags(
+                .setPkgFlags(PackageInfoUtils.appInfoFlags(parsedPackage, pkgSetting),
                         PackageInfoUtils.appInfoPrivateFlags(parsedPackage, pkgSetting));
         if (parsedPackage.getLongVersionCode() != pkgSetting.getVersionCode()) {
             pkgSetting.setLongVersionCode(parsedPackage.getLongVersionCode());
diff --git a/services/core/java/com/android/server/pm/SettingBase.java b/services/core/java/com/android/server/pm/SettingBase.java
index b952f80..61a251e 100644
--- a/services/core/java/com/android/server/pm/SettingBase.java
+++ b/services/core/java/com/android/server/pm/SettingBase.java
@@ -146,6 +146,17 @@
         return this;
     }
 
+    /**
+     * Unconditionally set both mPkgFlags and mPkgPrivateFlags.
+     * Should not be used outside pkgSetting initialization or update.
+     */
+    SettingBase setPkgFlags(int flags, int privateFlags) {
+        this.mPkgFlags = flags;
+        this.mPkgPrivateFlags = privateFlags;
+        onChanged();
+        return this;
+    }
+
     public int getFlags() {
         return mPkgFlags;
     }
diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java
index 6400502..7437b14 100644
--- a/services/core/java/com/android/server/pm/Settings.java
+++ b/services/core/java/com/android/server/pm/Settings.java
@@ -987,8 +987,7 @@
                     // Update new package state.
                     .setLastModifiedTime(codePath.lastModified())
                     .setDomainSetId(domainSetId);
-            pkgSetting.setFlags(pkgFlags)
-                    .setPrivateFlags(pkgPrivateFlags);
+            pkgSetting.setPkgFlags(pkgFlags, pkgPrivateFlags);
         } else {
             pkgSetting = new PackageSetting(pkgName, realPkgName, codePath,
                     legacyNativeLibraryPath, primaryCpuAbi, secondaryCpuAbi,
@@ -1175,15 +1174,15 @@
                     .setUsesStaticLibrariesVersions(null);
         }
 
-        // These two flags are preserved from the existing PackageSetting. Copied from prior code,
-        // unclear if this is actually necessary.
-        boolean wasExternalStorage = (pkgSetting.getFlags()
-                & ApplicationInfo.FLAG_EXTERNAL_STORAGE) != 0;
-        if (wasExternalStorage) {
-            pkgFlags |= ApplicationInfo.FLAG_EXTERNAL_STORAGE;
-        } else {
-            pkgFlags &= ~ApplicationInfo.FLAG_EXTERNAL_STORAGE;
-        }
+        // If what we are scanning is a system (and possibly privileged) package,
+        // then make it so, regardless of whether it was previously installed only
+        // in the data partition. Reset first.
+        int newPkgFlags = pkgSetting.getFlags();
+        newPkgFlags &= ~ApplicationInfo.FLAG_SYSTEM;
+        newPkgFlags |= pkgFlags & ApplicationInfo.FLAG_SYSTEM;
+        // Only set pkgFlags.
+        pkgSetting.setPkgFlags(newPkgFlags, pkgSetting.getPrivateFlags());
+
         boolean wasRequiredForSystemUser = (pkgSetting.getPrivateFlags()
                 & ApplicationInfo.PRIVATE_FLAG_REQUIRED_FOR_SYSTEM_USER) != 0;
         if (wasRequiredForSystemUser) {
@@ -1191,9 +1190,7 @@
         } else {
             pkgPrivateFlags &= ~ApplicationInfo.PRIVATE_FLAG_REQUIRED_FOR_SYSTEM_USER;
         }
-
-        pkgSetting.setFlags(pkgFlags)
-                .setPrivateFlags(pkgPrivateFlags);
+        pkgSetting.setPrivateFlags(pkgPrivateFlags);
     }
 
     /**
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 0a58044..a044e60 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -7738,6 +7738,9 @@
         // relatively fixed.
         overrideConfig.colorMode = fullConfig.colorMode;
         overrideConfig.densityDpi = fullConfig.densityDpi;
+        // The smallest screen width is the short side of screen bounds. Because the bounds
+        // and density won't be changed, smallestScreenWidthDp is also fixed.
+        overrideConfig.smallestScreenWidthDp = fullConfig.smallestScreenWidthDp;
         if (info.isFixedOrientation()) {
             // lock rotation too. When in size-compat, onConfigurationChanged will watch for and
             // apply runtime rotation changes.
@@ -7834,7 +7837,7 @@
             // computed accordingly.
             if (!matchParentBounds()) {
                 getTaskFragment().computeConfigResourceOverrides(resolvedConfig,
-                        newParentConfiguration, areBoundsLetterboxed());
+                        newParentConfiguration);
             }
         // If activity in fullscreen mode is letterboxed because of fixed orientation then bounds
         // are already calculated in resolveFixedOrientationConfiguration.
@@ -8005,8 +8008,7 @@
         }
 
         // Since bounds has changed, the configuration needs to be computed accordingly.
-        getTaskFragment().computeConfigResourceOverrides(resolvedConfig, newParentConfiguration,
-                areBoundsLetterboxed());
+        getTaskFragment().computeConfigResourceOverrides(resolvedConfig, newParentConfiguration);
     }
 
     void recomputeConfiguration() {
@@ -8222,7 +8224,7 @@
         // Calculate app bounds using fixed orientation bounds because they will be needed later
         // for comparison with size compat app bounds in {@link resolveSizeCompatModeConfiguration}.
         getTaskFragment().computeConfigResourceOverrides(getResolvedOverrideConfiguration(),
-                newParentConfig, mCompatDisplayInsets, areBoundsLetterboxed());
+                newParentConfig);
         mLetterboxBoundsForFixedOrientationAndAspectRatio = new Rect(resolvedBounds);
     }
 
@@ -8250,7 +8252,7 @@
             // Compute the configuration based on the resolved bounds. If aspect ratio doesn't
             // restrict, the bounds should be the requested override bounds.
             getTaskFragment().computeConfigResourceOverrides(resolvedConfig, newParentConfiguration,
-                    getFixedRotationTransformDisplayInfo(), areBoundsLetterboxed());
+                    getFixedRotationTransformDisplayInfo());
         }
     }
 
@@ -8314,7 +8316,7 @@
         // are calculated in compat container space. The actual position on screen will be applied
         // later, so the calculation is simpler that doesn't need to involve offset from parent.
         getTaskFragment().computeConfigResourceOverrides(resolvedConfig, newParentConfiguration,
-                mCompatDisplayInsets,  areBoundsLetterboxed());
+                mCompatDisplayInsets);
         // Use current screen layout as source because the size of app is independent to parent.
         resolvedConfig.screenLayout = TaskFragment.computeScreenLayoutOverride(
                 getConfiguration().screenLayout, resolvedConfig.screenWidthDp,
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index d9c509c..90b77f7 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -67,6 +67,7 @@
 import static android.view.WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL;
 import static android.view.WindowManager.LayoutParams.INVALID_WINDOW_TYPE;
 import static android.view.WindowManager.LayoutParams.LAST_APPLICATION_WINDOW;
+import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_UNRESTRICTED_GESTURE_EXCLUSION;
 import static android.view.WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN;
 import static android.view.WindowManager.LayoutParams.SOFT_INPUT_STATE_HIDDEN;
 import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_STARTING;
@@ -5574,11 +5575,13 @@
     private static boolean needsGestureExclusionRestrictions(WindowState win,
             boolean ignoreRequest) {
         final int type = win.mAttrs.type;
+        final int privateFlags = win.mAttrs.privateFlags;
         final boolean stickyHideNav =
                 !win.getRequestedVisibility(ITYPE_NAVIGATION_BAR)
                         && win.mAttrs.insetsFlags.behavior == BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE;
         return (!stickyHideNav || ignoreRequest) && type != TYPE_INPUT_METHOD
-                && type != TYPE_NOTIFICATION_SHADE && win.getActivityType() != ACTIVITY_TYPE_HOME;
+                && type != TYPE_NOTIFICATION_SHADE && win.getActivityType() != ACTIVITY_TYPE_HOME
+                && (privateFlags & PRIVATE_FLAG_UNRESTRICTED_GESTURE_EXCLUSION) == 0;
     }
 
     /**
diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java
index 98a51a9..4a7a8bd 100644
--- a/services/core/java/com/android/server/wm/DisplayPolicy.java
+++ b/services/core/java/com/android/server/wm/DisplayPolicy.java
@@ -47,6 +47,7 @@
 import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_FORCE_DRAW_BAR_BACKGROUNDS;
 import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_INTERCEPT_GLOBAL_DRAG_AND_DROP;
 import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY;
+import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_UNRESTRICTED_GESTURE_EXCLUSION;
 import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
 import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD;
 import static android.view.WindowManager.LayoutParams.TYPE_NAVIGATION_BAR;
@@ -974,6 +975,10 @@
             }
         }
 
+        if (!win.mSession.mCanSetUnrestrictedGestureExclusion) {
+            attrs.privateFlags &= ~PRIVATE_FLAG_UNRESTRICTED_GESTURE_EXCLUSION;
+        }
+
         // Check if alternate bars positions were updated.
         if (mStatusBarAlt == win) {
             mStatusBarAltPosition = getAltBarPosition(attrs);
diff --git a/services/core/java/com/android/server/wm/DisplayRotation.java b/services/core/java/com/android/server/wm/DisplayRotation.java
index b9d8319..3d91921 100644
--- a/services/core/java/com/android/server/wm/DisplayRotation.java
+++ b/services/core/java/com/android/server/wm/DisplayRotation.java
@@ -549,7 +549,7 @@
             // Go through all tasks and collect them before the rotation
             // TODO(shell-transitions): move collect() to onConfigurationChange once wallpaper
             //       handling is synchronized.
-            mDisplayContent.mTransitionController.collectForDisplayChange(mDisplayContent,
+            mDisplayContent.mTransitionController.collectForDisplayAreaChange(mDisplayContent,
                     null /* use collecting transition */);
         }
         mService.mAtmService.deferWindowLayout();
diff --git a/services/core/java/com/android/server/wm/PhysicalDisplaySwitchTransitionLauncher.java b/services/core/java/com/android/server/wm/PhysicalDisplaySwitchTransitionLauncher.java
index d209f08..64749cf 100644
--- a/services/core/java/com/android/server/wm/PhysicalDisplaySwitchTransitionLauncher.java
+++ b/services/core/java/com/android/server/wm/PhysicalDisplaySwitchTransitionLauncher.java
@@ -101,7 +101,7 @@
 
         if (t != null) {
             mDisplayContent.mAtmService.startLaunchPowerMode(POWER_MODE_REASON_CHANGE_DISPLAY);
-            mTransitionController.collectForDisplayChange(mDisplayContent, t);
+            mTransitionController.collectForDisplayAreaChange(mDisplayContent, t);
             mTransition = t;
         }
     }
diff --git a/services/core/java/com/android/server/wm/RecentsAnimation.java b/services/core/java/com/android/server/wm/RecentsAnimation.java
index f840171..ffe3374 100644
--- a/services/core/java/com/android/server/wm/RecentsAnimation.java
+++ b/services/core/java/com/android/server/wm/RecentsAnimation.java
@@ -520,7 +520,7 @@
     }
 
     private boolean matchesTarget(Task task) {
-        return task.mUserId == mUserId
+        return task.getNonFinishingActivityCount() > 0 && task.mUserId == mUserId
                 && task.getBaseIntent().getComponent().equals(mTargetIntent.getComponent());
     }
 }
diff --git a/services/core/java/com/android/server/wm/Session.java b/services/core/java/com/android/server/wm/Session.java
index 9b013da..3577545 100644
--- a/services/core/java/com/android/server/wm/Session.java
+++ b/services/core/java/com/android/server/wm/Session.java
@@ -19,6 +19,7 @@
 import static android.Manifest.permission.HIDE_NON_SYSTEM_OVERLAY_WINDOWS;
 import static android.Manifest.permission.HIDE_OVERLAY_WINDOWS;
 import static android.Manifest.permission.INTERNAL_SYSTEM_WINDOW;
+import static android.Manifest.permission.SET_UNRESTRICTED_GESTURE_EXCLUSION;
 import static android.Manifest.permission.SET_UNRESTRICTED_KEEP_CLEAR_AREAS;
 import static android.Manifest.permission.START_TASKS_FROM_RECENTS;
 import static android.Manifest.permission.SYSTEM_APPLICATION_OVERLAY;
@@ -109,6 +110,7 @@
 
     final boolean mCanCreateSystemApplicationOverlay;
     final boolean mCanHideNonSystemOverlayWindows;
+    final boolean mCanSetUnrestrictedGestureExclusion;
     private AlertWindowNotification mAlertWindowNotification;
     private boolean mShowingAlertWindowNotificationAllowed;
     private boolean mClientDead = false;
@@ -139,6 +141,9 @@
         mSetsUnrestrictedKeepClearAreas =
                 service.mContext.checkCallingOrSelfPermission(SET_UNRESTRICTED_KEEP_CLEAR_AREAS)
                         == PERMISSION_GRANTED;
+        mCanSetUnrestrictedGestureExclusion =
+                service.mContext.checkCallingOrSelfPermission(SET_UNRESTRICTED_GESTURE_EXCLUSION)
+                        == PERMISSION_GRANTED;
         mShowingAlertWindowNotificationAllowed = mService.mShowAlertWindowNotifications;
         mDragDropController = mService.mDragDropController;
         StringBuilder sb = new StringBuilder();
diff --git a/services/core/java/com/android/server/wm/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java
index 21c5886..f8a9d46 100644
--- a/services/core/java/com/android/server/wm/TaskFragment.java
+++ b/services/core/java/com/android/server/wm/TaskFragment.java
@@ -1957,37 +1957,29 @@
     void computeConfigResourceOverrides(@NonNull Configuration inOutConfig,
             @NonNull Configuration parentConfig) {
         computeConfigResourceOverrides(inOutConfig, parentConfig, null /* overrideDisplayInfo */,
-                null /* compatInsets */, false /* areBoundsLetterboxed */);
+                null /* compatInsets */);
     }
 
     void computeConfigResourceOverrides(@NonNull Configuration inOutConfig,
-            @NonNull Configuration parentConfig, boolean areBoundsLetterboxed) {
-        computeConfigResourceOverrides(inOutConfig, parentConfig, null /* overrideDisplayInfo */,
-                null /* compatInsets */, areBoundsLetterboxed);
-    }
-
-    void computeConfigResourceOverrides(@NonNull Configuration inOutConfig,
-            @NonNull Configuration parentConfig, @Nullable DisplayInfo overrideDisplayInfo,
-            boolean areBoundsLetterboxed) {
+            @NonNull Configuration parentConfig, @Nullable DisplayInfo overrideDisplayInfo) {
         if (overrideDisplayInfo != null) {
             // Make sure the screen related configs can be computed by the provided display info.
             inOutConfig.screenLayout = Configuration.SCREENLAYOUT_UNDEFINED;
             invalidateAppBoundsConfig(inOutConfig);
         }
         computeConfigResourceOverrides(inOutConfig, parentConfig, overrideDisplayInfo,
-                null /* compatInsets */, areBoundsLetterboxed);
+                null /* compatInsets */);
     }
 
     void computeConfigResourceOverrides(@NonNull Configuration inOutConfig,
             @NonNull Configuration parentConfig,
-            @Nullable ActivityRecord.CompatDisplayInsets compatInsets,
-            boolean areBoundsLetterboxed) {
+            @Nullable ActivityRecord.CompatDisplayInsets compatInsets) {
         if (compatInsets != null) {
             // Make sure the app bounds can be computed by the compat insets.
             invalidateAppBoundsConfig(inOutConfig);
         }
         computeConfigResourceOverrides(inOutConfig, parentConfig, null /* overrideDisplayInfo */,
-                compatInsets, areBoundsLetterboxed);
+                compatInsets);
     }
 
     /**
@@ -2014,8 +2006,7 @@
      **/
     void computeConfigResourceOverrides(@NonNull Configuration inOutConfig,
             @NonNull Configuration parentConfig, @Nullable DisplayInfo overrideDisplayInfo,
-            @Nullable ActivityRecord.CompatDisplayInsets compatInsets,
-            boolean areBoundsLetterboxed) {
+            @Nullable ActivityRecord.CompatDisplayInsets compatInsets) {
         int windowingMode = inOutConfig.windowConfiguration.getWindowingMode();
         if (windowingMode == WINDOWING_MODE_UNDEFINED) {
             windowingMode = parentConfig.windowConfiguration.getWindowingMode();
@@ -2122,7 +2113,6 @@
                         : overrideScreenHeightDp;
             }
 
-            // TODO(b/238331848): Consider simplifying logic that computes smallestScreenWidthDp.
             if (inOutConfig.smallestScreenWidthDp
                     == Configuration.SMALLEST_SCREEN_WIDTH_DP_UNDEFINED) {
                 // When entering to or exiting from Pip, the PipTaskOrganizer will set the
@@ -2138,10 +2128,9 @@
                     // task, because they should not be affected by insets.
                     inOutConfig.smallestScreenWidthDp = (int) (0.5f
                             + Math.min(mTmpFullBounds.width(), mTmpFullBounds.height()) / density);
-                } else if (isEmbedded() || areBoundsLetterboxed || customContainerPolicy) {
-                    // For embedded TFs and activities that are letteboxed or eligible for size
-                    // compat mode, the smallest width should be updated. Otherwise, inherit from
-                    // the parent task would result in applications loaded wrong resource.
+                } else if (isEmbedded()) {
+                    // For embedded TFs, the smallest width should be updated. Otherwise, inherit
+                    // from the parent task would result in applications loaded wrong resource.
                     inOutConfig.smallestScreenWidthDp =
                             Math.min(inOutConfig.screenWidthDp, inOutConfig.screenHeightDp);
                 }
diff --git a/services/core/java/com/android/server/wm/TransitionController.java b/services/core/java/com/android/server/wm/TransitionController.java
index 88572a9..a02be25 100644
--- a/services/core/java/com/android/server/wm/TransitionController.java
+++ b/services/core/java/com/android/server/wm/TransitionController.java
@@ -460,23 +460,26 @@
      * Collects the window containers which need to be synced with the changing display (e.g.
      * rotating) to the given transition or the current collecting transition.
      */
-    void collectForDisplayChange(@NonNull DisplayContent dc, @Nullable Transition incoming) {
+    void collectForDisplayAreaChange(@NonNull DisplayArea<?> wc, @Nullable Transition incoming) {
         if (incoming == null) incoming = mCollectingTransition;
         if (incoming == null) return;
         final Transition transition = incoming;
         // Collect all visible tasks.
-        dc.forAllLeafTasks(task -> {
+        wc.forAllLeafTasks(task -> {
             if (task.isVisible()) {
                 transition.collect(task);
             }
         }, true /* traverseTopToBottom */);
         // Collect all visible non-app windows which need to be drawn before the animation starts.
-        dc.forAllWindows(w -> {
-            if (w.mActivityRecord == null && w.isVisible() && !isCollecting(w.mToken)
-                    && dc.shouldSyncRotationChange(w)) {
-                transition.collect(w.mToken);
-            }
-        }, true /* traverseTopToBottom */);
+        final DisplayContent dc = wc.asDisplayContent();
+        if (dc != null) {
+            wc.forAllWindows(w -> {
+                if (w.mActivityRecord == null && w.isVisible() && !isCollecting(w.mToken)
+                        && dc.shouldSyncRotationChange(w)) {
+                    transition.collect(w.mToken);
+                }
+            }, true /* traverseTopToBottom */);
+        }
     }
 
     /** @see Transition#mStatusBarTransitionDelay */
diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java
index 6bb5ece..ee64354 100644
--- a/services/core/java/com/android/server/wm/WindowOrganizerController.java
+++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java
@@ -397,7 +397,7 @@
                     // Go through all tasks and collect them before the rotation
                     // TODO(shell-transitions): move collect() to onConfigurationChange once
                     //       wallpaper handling is synchronized.
-                    dc.mTransitionController.collectForDisplayChange(dc, transition);
+                    dc.mTransitionController.collectForDisplayAreaChange(dc, transition);
                     dc.sendNewConfiguration();
                     effects |= TRANSACT_EFFECTS_LIFECYCLE;
                 }
@@ -421,6 +421,15 @@
                     addToSyncSet(syncId, wc);
                 }
                 if (transition != null) transition.collect(wc);
+                final DisplayArea da = wc.asDisplayArea();
+                // Only check DisplayArea here as a similar thing is done for DisplayContent above.
+                if (da != null && wc.asDisplayContent() == null
+                        && entry.getValue().getWindowingMode() != da.getWindowingMode()) {
+                    // Go through all tasks and collect them before changing the windowing mode of a
+                    // display-level container.
+                    // TODO(shell-transitions): handle this more elegantly.
+                    da.mTransitionController.collectForDisplayAreaChange(da, transition);
+                }
 
                 if ((entry.getValue().getChangeMask()
                         & WindowContainerTransaction.Change.CHANGE_FORCE_NO_PIP) != 0) {
diff --git a/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java
index 67ef7f5..80de823 100644
--- a/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java
@@ -2357,10 +2357,15 @@
         mBinder.set(TEST_CALLING_PACKAGE, RTC_WAKEUP, 1234, WINDOW_EXACT, 0, 0,
                 alarmPi, null, null, null, alarmClock);
 
+        final ArgumentCaptor<Bundle> bundleCaptor = ArgumentCaptor.forClass(Bundle.class);
         verify(mService).setImpl(eq(RTC_WAKEUP), eq(1234L), eq(WINDOW_EXACT), eq(0L),
                 eq(alarmPi), isNull(), isNull(), eq(FLAG_STANDALONE | FLAG_WAKE_FROM_IDLE),
-                isNull(), eq(alarmClock), eq(TEST_CALLING_UID), eq(TEST_CALLING_PACKAGE), isNull(),
-                eq(EXACT_ALLOW_REASON_COMPAT));
+                isNull(), eq(alarmClock), eq(TEST_CALLING_UID), eq(TEST_CALLING_PACKAGE),
+                bundleCaptor.capture(), eq(EXACT_ALLOW_REASON_COMPAT));
+
+        final BroadcastOptions idleOptions = new BroadcastOptions(bundleCaptor.getValue());
+        final int type = idleOptions.getTemporaryAppAllowlistType();
+        assertEquals(TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_ALLOWED, type);
     }
 
     @Test
diff --git a/services/tests/servicestests/src/com/android/server/display/DisplayDeviceConfigTest.java b/services/tests/servicestests/src/com/android/server/display/DisplayDeviceConfigTest.java
new file mode 100644
index 0000000..7939303
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/display/DisplayDeviceConfigTest.java
@@ -0,0 +1,182 @@
+/*
+ * 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.server.display;
+
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+
+@SmallTest
+@Presubmit
+@RunWith(AndroidJUnit4.class)
+public final class DisplayDeviceConfigTest {
+    private DisplayDeviceConfig mDisplayDeviceConfig;
+    @Mock
+    private Context mContext;
+
+    @Mock
+    private Resources mResources;
+
+    @Before
+    public void setUp() throws IOException {
+        MockitoAnnotations.initMocks(this);
+        when(mContext.getResources()).thenReturn(mResources);
+        mockDeviceConfigs();
+        try {
+            Path tempFile = Files.createTempFile("display_config", ".tmp");
+            Files.write(tempFile, getContent().getBytes(StandardCharsets.UTF_8));
+            mDisplayDeviceConfig = new DisplayDeviceConfig(mContext);
+            mDisplayDeviceConfig.initFromFile(tempFile.toFile());
+        } catch (IOException e) {
+            throw new IOException("Failed to setup the display device config.", e);
+        }
+    }
+
+    @Test
+    public void testConfigValues() {
+        assertEquals(mDisplayDeviceConfig.getAmbientHorizonLong(), 5000);
+        assertEquals(mDisplayDeviceConfig.getAmbientHorizonShort(), 50);
+        assertEquals(mDisplayDeviceConfig.getBrightnessRampDecreaseMaxMillis(), 3000);
+        assertEquals(mDisplayDeviceConfig.getBrightnessRampIncreaseMaxMillis(), 2000);
+        assertEquals(mDisplayDeviceConfig.getAmbientLuxBrighteningMinThreshold(), 10.0f, 0.0f);
+        assertEquals(mDisplayDeviceConfig.getAmbientLuxDarkeningMinThreshold(), 2.0f, 0.0f);
+        assertEquals(mDisplayDeviceConfig.getBrightnessRampFastDecrease(), 0.01f, 0.0f);
+        assertEquals(mDisplayDeviceConfig.getBrightnessRampFastIncrease(), 0.02f, 0.0f);
+        assertEquals(mDisplayDeviceConfig.getBrightnessRampSlowIncrease(), 0.04f, 0.0f);
+        assertEquals(mDisplayDeviceConfig.getBrightnessRampSlowDecrease(), 0.03f, 0.0f);
+        assertEquals(mDisplayDeviceConfig.getBrightnessDefault(), 0.5f, 0.0f);
+        assertArrayEquals(mDisplayDeviceConfig.getBrightness(), new float[]{0.0f, 0.62f, 1.0f},
+                0.0f);
+        assertArrayEquals(mDisplayDeviceConfig.getNits(), new float[]{2.0f, 500.0f, 800.0f}, 0.0f);
+        assertArrayEquals(mDisplayDeviceConfig.getBacklight(), new float[]{0.0f, 0.62f, 1.0f},
+                0.0f);
+        assertEquals(mDisplayDeviceConfig.getScreenBrighteningMinThreshold(), 0.001, 0.000001f);
+        assertEquals(mDisplayDeviceConfig.getScreenDarkeningMinThreshold(), 0.002, 0.000001f);
+
+        // Todo(brup): Add asserts for BrightnessThrottlingData, DensityMapping,
+        // HighBrightnessModeData AmbientLightSensor, RefreshRateLimitations and ProximitySensor.
+        // Also add test for the case where optional display configs are null
+    }
+
+    private String getContent() {
+        return "<?xml version='1.0' encoding='utf-8' standalone='yes' ?>\n"
+                + "<displayConfiguration>\n"
+                +   "<screenBrightnessMap>\n"
+                +       "<point>\n"
+                +           "<value>0.0</value>\n"
+                +           "<nits>2.0</nits>\n"
+                +       "</point>\n"
+                +       "<point>\n"
+                +           "<value>0.62</value>\n"
+                +           "<nits>500.0</nits>\n"
+                +       "</point>\n"
+                +       "<point>\n"
+                +           "<value>1.0</value>\n"
+                +           "<nits>800.0</nits>\n"
+                +       "</point>\n"
+                +   "</screenBrightnessMap>\n"
+                +   "<highBrightnessMode enabled=\"true\">\n"
+                +       "<transitionPoint>0.62</transitionPoint>\n"
+                +       "<minimumLux>10000</minimumLux>\n"
+                +       "<timing>\n"
+                +           "<!-- allow for 5 minutes out of every 30 minutes -->\n"
+                +           "<timeWindowSecs>1800</timeWindowSecs>\n"
+                +           "<timeMaxSecs>300</timeMaxSecs>\n"
+                +           "<timeMinSecs>60</timeMinSecs>\n"
+                +       "</timing>\n"
+                +       "<refreshRate>\n"
+                +           "<minimum>120</minimum>\n"
+                +           "<maximum>120</maximum>\n"
+                +       "</refreshRate>\n"
+                +       "<thermalStatusLimit>light</thermalStatusLimit>\n"
+                +       "<allowInLowPowerMode>false</allowInLowPowerMode>\n"
+                +   "</highBrightnessMode>\n"
+                +   "<ambientBrightnessChangeThresholds>\n"
+                +       "<brighteningThresholds>\n"
+                +           "<minimum>10</minimum>\n"
+                +       "</brighteningThresholds>\n"
+                +       "<darkeningThresholds>\n"
+                +           "<minimum>2</minimum>\n"
+                +       "</darkeningThresholds>\n"
+                +   "</ambientBrightnessChangeThresholds>\n"
+                +   "<screenBrightnessRampFastDecrease>0.01</screenBrightnessRampFastDecrease> "
+                +   "<screenBrightnessRampFastIncrease>0.02</screenBrightnessRampFastIncrease>  "
+                +   "<screenBrightnessRampSlowDecrease>0.03</screenBrightnessRampSlowDecrease>"
+                +   "<screenBrightnessRampSlowIncrease>0.04</screenBrightnessRampSlowIncrease>"
+                +   "<screenBrightnessRampIncreaseMaxMillis>"
+                +       "2000"
+                +   "</screenBrightnessRampIncreaseMaxMillis>"
+                +   "<screenBrightnessRampDecreaseMaxMillis>"
+                +       "3000"
+                +   "</screenBrightnessRampDecreaseMaxMillis>"
+                +   "<ambientLightHorizonLong>5000</ambientLightHorizonLong>\n"
+                +   "<ambientLightHorizonShort>50</ambientLightHorizonShort>\n"
+                +   "<displayBrightnessChangeThresholds>"
+                +       "<brighteningThresholds>"
+                +           "<minimum>"
+                +               "0.001"
+                +           "</minimum>"
+                +       "</brighteningThresholds>"
+                +       "<darkeningThresholds>"
+                +           "<minimum>"
+                +               "0.002"
+                +           "</minimum>"
+                +       "</darkeningThresholds>"
+                +   "</displayBrightnessChangeThresholds>"
+                +   "<screenBrightnessRampIncreaseMaxMillis>"
+                +       "2000"
+                +    "</screenBrightnessRampIncreaseMaxMillis>\n"
+                +   "<thermalThrottling>\n"
+                +       "<brightnessThrottlingMap>\n"
+                +           "<brightnessThrottlingPoint>\n"
+                +               "<thermalStatus>emergency</thermalStatus>\n"
+                +               "<!-- Throttling to 250 nits: (250-2.0)/(500-2.0)*(0.62-0.0)+0"
+                +               ".0 = 0.30875502 -->\n"
+                +               "<brightness>0.30875502</brightness>\n"
+                +           "</brightnessThrottlingPoint>\n"
+                +       "</brightnessThrottlingMap>\n"
+                +   "</thermalThrottling>\n"
+                + "</displayConfiguration>\n";
+    }
+
+    private void mockDeviceConfigs() {
+        when(mResources.getFloat(com.android.internal.R.dimen
+                .config_screenBrightnessSettingDefaultFloat)).thenReturn(0.5f);
+        when(mResources.getFloat(com.android.internal.R.dimen
+                .config_screenBrightnessSettingMaximumFloat)).thenReturn(1.0f);
+    }
+}
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
index 11a7c7d..1f07b20 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
@@ -47,8 +47,10 @@
 import static android.view.WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN;
 import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
 import static android.view.WindowManager.LayoutParams.FLAG_SECURE;
+import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
 import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES;
 import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_NO_MOVE_ANIMATION;
+import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_UNRESTRICTED_GESTURE_EXCLUSION;
 import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION;
 import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_ATTACHED_DIALOG;
 import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
@@ -1402,6 +1404,28 @@
         win.setHasSurface(false);
     }
 
+    @Test
+    public void testCalculateSystemGestureExclusion_unrestricted() throws Exception {
+        mWm.mConstants.mSystemGestureExcludedByPreQStickyImmersive = true;
+
+        final DisplayContent dc = createNewDisplay();
+        final WindowState win = createWindow(null, TYPE_BASE_APPLICATION, dc, "win");
+        win.getAttrs().flags |= FLAG_LAYOUT_IN_SCREEN | FLAG_LAYOUT_INSET_DECOR;
+        win.getAttrs().layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
+        win.getAttrs().privateFlags |= PRIVATE_FLAG_UNRESTRICTED_GESTURE_EXCLUSION;
+        win.setSystemGestureExclusion(Collections.singletonList(dc.getBounds()));
+
+        performLayout(dc);
+
+        win.setHasSurface(true);
+
+        final Region expected = Region.obtain();
+        expected.set(dc.getBounds());
+        assertEquals(expected, calculateSystemGestureExclusion(dc));
+
+        win.setHasSurface(false);
+    }
+
     @UseTestDisplay(addWindows = { W_ABOVE_ACTIVITY, W_ACTIVITY})
     @Test
     public void testRequestResizeForEmptyFrames() {
diff --git a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
index f2640d2..324e244 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
@@ -1496,79 +1496,6 @@
     }
 
     @Test
-    public void testComputeConfigResourceOverrides_unresizableApp() {
-        // Set up a display in landscape and ignoring orientation request.
-        setUpDisplaySizeWithApp(2800, 1400);
-        mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */);
-
-        prepareUnresizable(mActivity, SCREEN_ORIENTATION_PORTRAIT);
-
-        final Rect activityBounds = new Rect(mActivity.getBounds());
-
-        int originalScreenWidthDp = mActivity.getConfiguration().screenWidthDp;
-        int originalScreenHeighthDp = mActivity.getConfiguration().screenHeightDp;
-
-        // App should launch in fixed orientation letterbox.
-        // Activity bounds should be 700x1400 with the ratio as the display.
-        assertTrue(mActivity.isLetterboxedForFixedOrientationAndAspectRatio());
-        assertFitted();
-        assertEquals(originalScreenWidthDp, mActivity.getConfiguration().smallestScreenWidthDp);
-        assertTrue(originalScreenWidthDp < originalScreenHeighthDp);
-
-        // Rotate display to portrait.
-        rotateDisplay(mActivity.mDisplayContent, ROTATION_90);
-
-        // After we rotate, the activity should go in the size-compat mode and report the same
-        // configuration values.
-        assertScaled();
-        assertEquals(originalScreenWidthDp, mActivity.getConfiguration().smallestScreenWidthDp);
-        assertEquals(originalScreenWidthDp, mActivity.getConfiguration().screenWidthDp);
-        assertEquals(originalScreenHeighthDp, mActivity.getConfiguration().screenHeightDp);
-
-        // Restart activity
-        mActivity.restartProcessIfVisible();
-
-        // Now configuration should be updated
-        assertFitted();
-        assertNotEquals(originalScreenWidthDp, mActivity.getConfiguration().screenWidthDp);
-        assertNotEquals(originalScreenHeighthDp, mActivity.getConfiguration().screenHeightDp);
-        assertEquals(mActivity.getConfiguration().screenWidthDp,
-                mActivity.getConfiguration().smallestScreenWidthDp);
-    }
-
-    @Test
-    public void testComputeConfigResourceOverrides_resizableFixedOrientationActivity() {
-        // Set up a display in landscape and ignoring orientation request.
-        setUpDisplaySizeWithApp(2800, 1400);
-        mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */);
-
-        // Portrait fixed app without max aspect.
-        prepareLimitedBounds(mActivity, SCREEN_ORIENTATION_PORTRAIT, false /* isUnresizable */);
-
-        final Rect activityBounds = new Rect(mActivity.getBounds());
-
-        int originalScreenWidthDp = mActivity.getConfiguration().screenWidthDp;
-        int originalScreenHeighthDp = mActivity.getConfiguration().screenHeightDp;
-
-        // App should launch in fixed orientation letterbox.
-        // Activity bounds should be 700x1400 with the ratio as the display.
-        assertTrue(mActivity.isLetterboxedForFixedOrientationAndAspectRatio());
-        assertFitted();
-        assertEquals(originalScreenWidthDp, mActivity.getConfiguration().smallestScreenWidthDp);
-        assertTrue(originalScreenWidthDp < originalScreenHeighthDp);
-
-        // Rotate display to portrait.
-        rotateDisplay(mActivity.mDisplayContent, ROTATION_90);
-
-        // Now configuration should be updated
-        assertFitted();
-        assertNotEquals(originalScreenWidthDp, mActivity.getConfiguration().screenWidthDp);
-        assertNotEquals(originalScreenHeighthDp, mActivity.getConfiguration().screenHeightDp);
-        assertEquals(mActivity.getConfiguration().screenWidthDp,
-                mActivity.getConfiguration().smallestScreenWidthDp);
-    }
-
-    @Test
     public void testSplitAspectRatioForUnresizablePortraitApps() {
         // Set up a display in landscape and ignoring orientation request.
         int screenWidth = 1600;
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskTests.java
index 1f03039..f4323db 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskTests.java
@@ -705,8 +705,7 @@
         final ActivityRecord.CompatDisplayInsets compatInsets =
                 new ActivityRecord.CompatDisplayInsets(
                         display, activity, /* fixedOrientationBounds= */ null);
-        task.computeConfigResourceOverrides(
-                inOutConfig, parentConfig, compatInsets, /* areBoundsLetterboxed */ true);
+        task.computeConfigResourceOverrides(inOutConfig, parentConfig, compatInsets);
 
         assertEquals(largerLandscapeBounds, inOutConfig.windowConfiguration.getAppBounds());
         final float density = parentConfig.densityDpi * DisplayMetrics.DENSITY_DEFAULT_SCALE;
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java
index 1715a29..c8ea70c 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java
@@ -22,8 +22,8 @@
 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_PORTRAIT;
 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSET;
 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
-import static android.view.InsetsState.ITYPE_LOCAL_NAVIGATION_BAR_1;
-import static android.view.InsetsState.ITYPE_LOCAL_NAVIGATION_BAR_2;
+import static android.view.InsetsState.ITYPE_BOTTOM_GENERIC_OVERLAY;
+import static android.view.InsetsState.ITYPE_TOP_GENERIC_OVERLAY;
 import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
 import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
 import static android.view.WindowManager.TRANSIT_CLOSE;
@@ -1302,40 +1302,42 @@
                 TYPE_BASE_APPLICATION);
         attrs2.setTitle("AppWindow2");
         activity2.addWindow(createWindowState(attrs2, activity2));
-        Rect navigationBarInsetsRect1 = new Rect(0, 200, 1080, 700);
-        Rect navigationBarInsetsRect2 = new Rect(0, 0, 1080, 200);
+        Rect genericOverlayInsetsRect1 = new Rect(0, 200, 1080, 700);
+        Rect genericOverlayInsetsRect2 = new Rect(0, 0, 1080, 200);
 
-        rootTask.addLocalRectInsetsSourceProvider(navigationBarInsetsRect1,
-                new int[]{ITYPE_LOCAL_NAVIGATION_BAR_1});
-        container.addLocalRectInsetsSourceProvider(navigationBarInsetsRect2,
-                new int[]{ITYPE_LOCAL_NAVIGATION_BAR_2});
+        rootTask.addLocalRectInsetsSourceProvider(genericOverlayInsetsRect1,
+                new int[]{ITYPE_TOP_GENERIC_OVERLAY});
+        container.addLocalRectInsetsSourceProvider(genericOverlayInsetsRect2,
+                new int[]{ITYPE_BOTTOM_GENERIC_OVERLAY});
 
-        InsetsSource navigationBarInsetsProvider1Source = new InsetsSource(
-                ITYPE_LOCAL_NAVIGATION_BAR_1);
-        navigationBarInsetsProvider1Source.setFrame(navigationBarInsetsRect1);
-        navigationBarInsetsProvider1Source.setVisible(true);
-        InsetsSource navigationBarInsetsProvider2Source = new InsetsSource(
-                ITYPE_LOCAL_NAVIGATION_BAR_2);
-        navigationBarInsetsProvider2Source.setFrame(navigationBarInsetsRect2);
-        navigationBarInsetsProvider2Source.setVisible(true);
+        InsetsSource genericOverlayInsetsProvider1Source = new InsetsSource(
+                ITYPE_TOP_GENERIC_OVERLAY);
+        genericOverlayInsetsProvider1Source.setFrame(genericOverlayInsetsRect1);
+        genericOverlayInsetsProvider1Source.setVisible(true);
+        InsetsSource genericOverlayInsetsProvider2Source = new InsetsSource(
+                ITYPE_BOTTOM_GENERIC_OVERLAY);
+        genericOverlayInsetsProvider2Source.setFrame(genericOverlayInsetsRect2);
+        genericOverlayInsetsProvider2Source.setVisible(true);
 
         activity0.forAllWindows(window -> {
-            assertEquals(navigationBarInsetsRect1,
-                    window.getInsetsState().peekSource(ITYPE_LOCAL_NAVIGATION_BAR_1).getFrame());
+            assertEquals(genericOverlayInsetsRect1,
+                    window.getInsetsState().peekSource(ITYPE_TOP_GENERIC_OVERLAY).getFrame());
             assertEquals(null,
-                    window.getInsetsState().peekSource(ITYPE_LOCAL_NAVIGATION_BAR_2));
+                    window.getInsetsState().peekSource(ITYPE_BOTTOM_GENERIC_OVERLAY));
         }, true);
         activity1.forAllWindows(window -> {
-            assertEquals(navigationBarInsetsRect1,
-                    window.getInsetsState().peekSource(ITYPE_LOCAL_NAVIGATION_BAR_1).getFrame());
-            assertEquals(navigationBarInsetsRect2,
-                    window.getInsetsState().peekSource(ITYPE_LOCAL_NAVIGATION_BAR_2).getFrame());
+            assertEquals(genericOverlayInsetsRect1,
+                    window.getInsetsState().peekSource(ITYPE_TOP_GENERIC_OVERLAY).getFrame());
+            assertEquals(genericOverlayInsetsRect2,
+                    window.getInsetsState().peekSource(ITYPE_BOTTOM_GENERIC_OVERLAY)
+                            .getFrame());
         }, true);
         activity2.forAllWindows(window -> {
-            assertEquals(navigationBarInsetsRect1,
-                    window.getInsetsState().peekSource(ITYPE_LOCAL_NAVIGATION_BAR_1).getFrame());
-            assertEquals(navigationBarInsetsRect2,
-                    window.getInsetsState().peekSource(ITYPE_LOCAL_NAVIGATION_BAR_2).getFrame());
+            assertEquals(genericOverlayInsetsRect1,
+                    window.getInsetsState().peekSource(ITYPE_TOP_GENERIC_OVERLAY).getFrame());
+            assertEquals(genericOverlayInsetsRect2,
+                    window.getInsetsState().peekSource(ITYPE_BOTTOM_GENERIC_OVERLAY)
+                            .getFrame());
         }, true);
     }
 
@@ -1344,7 +1346,7 @@
          /*
                 ___ rootTask ________________________________________
                |                  |                                  |
-          activity0      navigationBarInsetsProvider1    navigationBarInsetsProvider2
+          activity0      genericOverlayInsetsProvider1    genericOverlayInsetsProvider2
          */
         final Task rootTask = createTask(mDisplayContent);
 
@@ -1355,22 +1357,22 @@
         attrs.setTitle("AppWindow0");
         activity0.addWindow(createWindowState(attrs, activity0));
 
-        Rect navigationBarInsetsRect1 = new Rect(0, 200, 1080, 700);
-        Rect navigationBarInsetsRect2 = new Rect(0, 0, 1080, 200);
+        Rect genericOverlayInsetsRect1 = new Rect(0, 200, 1080, 700);
+        Rect genericOverlayInsetsRect2 = new Rect(0, 0, 1080, 200);
 
-        rootTask.addLocalRectInsetsSourceProvider(navigationBarInsetsRect1,
-                new int[]{ITYPE_LOCAL_NAVIGATION_BAR_1});
+        rootTask.addLocalRectInsetsSourceProvider(genericOverlayInsetsRect1,
+                new int[]{ITYPE_TOP_GENERIC_OVERLAY});
         activity0.forAllWindows(window -> {
-            assertEquals(navigationBarInsetsRect1,
-                    window.getInsetsState().peekSource(ITYPE_LOCAL_NAVIGATION_BAR_1).getFrame());
+            assertEquals(genericOverlayInsetsRect1,
+                    window.getInsetsState().peekSource(ITYPE_TOP_GENERIC_OVERLAY).getFrame());
         }, true);
 
-        rootTask.addLocalRectInsetsSourceProvider(navigationBarInsetsRect2,
-                new int[]{ITYPE_LOCAL_NAVIGATION_BAR_1});
+        rootTask.addLocalRectInsetsSourceProvider(genericOverlayInsetsRect2,
+                new int[]{ITYPE_TOP_GENERIC_OVERLAY});
 
         activity0.forAllWindows(window -> {
-            assertEquals(navigationBarInsetsRect2,
-                    window.getInsetsState().peekSource(ITYPE_LOCAL_NAVIGATION_BAR_1).getFrame());
+            assertEquals(genericOverlayInsetsRect2,
+                    window.getInsetsState().peekSource(ITYPE_TOP_GENERIC_OVERLAY).getFrame());
         }, true);
     }
 
@@ -1412,30 +1414,32 @@
         Rect navigationBarInsetsRect2 = new Rect(0, 0, 1080, 200);
 
         rootTask.addLocalRectInsetsSourceProvider(navigationBarInsetsRect1,
-                new int[]{ITYPE_LOCAL_NAVIGATION_BAR_1});
+                new int[]{ITYPE_TOP_GENERIC_OVERLAY});
         container.addLocalRectInsetsSourceProvider(navigationBarInsetsRect2,
-                new int[]{ITYPE_LOCAL_NAVIGATION_BAR_2});
+                new int[]{ITYPE_BOTTOM_GENERIC_OVERLAY});
         mDisplayContent.getInsetsStateController().onPostLayout();
-        rootTask.removeLocalInsetsSourceProvider(new int[]{ITYPE_LOCAL_NAVIGATION_BAR_1});
+        rootTask.removeLocalInsetsSourceProvider(new int[]{ITYPE_TOP_GENERIC_OVERLAY});
         mDisplayContent.getInsetsStateController().onPostLayout();
 
         activity0.forAllWindows(window -> {
             assertEquals(null,
-                    window.getInsetsState().peekSource(ITYPE_LOCAL_NAVIGATION_BAR_1));
+                    window.getInsetsState().peekSource(ITYPE_TOP_GENERIC_OVERLAY));
             assertEquals(null,
-                    window.getInsetsState().peekSource(ITYPE_LOCAL_NAVIGATION_BAR_2));
+                    window.getInsetsState().peekSource(ITYPE_BOTTOM_GENERIC_OVERLAY));
         }, true);
         activity1.forAllWindows(window -> {
             assertEquals(null,
-                    window.getInsetsState().peekSource(ITYPE_LOCAL_NAVIGATION_BAR_1));
+                    window.getInsetsState().peekSource(ITYPE_TOP_GENERIC_OVERLAY));
             assertEquals(navigationBarInsetsRect2,
-                    window.getInsetsState().peekSource(ITYPE_LOCAL_NAVIGATION_BAR_2).getFrame());
+                    window.getInsetsState().peekSource(ITYPE_BOTTOM_GENERIC_OVERLAY)
+                                    .getFrame());
         }, true);
         activity2.forAllWindows(window -> {
             assertEquals(null,
-                    window.getInsetsState().peekSource(ITYPE_LOCAL_NAVIGATION_BAR_1));
+                    window.getInsetsState().peekSource(ITYPE_TOP_GENERIC_OVERLAY));
             assertEquals(navigationBarInsetsRect2,
-                    window.getInsetsState().peekSource(ITYPE_LOCAL_NAVIGATION_BAR_2).getFrame());
+                    window.getInsetsState().peekSource(ITYPE_BOTTOM_GENERIC_OVERLAY)
+                            .getFrame());
         }, true);
     }
 
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java
index 5407412..84c2c55 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java
@@ -30,7 +30,7 @@
 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
 import static android.content.res.Configuration.SCREEN_HEIGHT_DP_UNDEFINED;
 import static android.content.res.Configuration.SCREEN_WIDTH_DP_UNDEFINED;
-import static android.view.InsetsState.ITYPE_LOCAL_NAVIGATION_BAR_1;
+import static android.view.InsetsState.ITYPE_TOP_GENERIC_OVERLAY;
 import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION;
 
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing;
@@ -779,11 +779,11 @@
         final WindowContainerTransaction wct = new WindowContainerTransaction();
         wct.addRectInsetsProvider(navigationBarInsetsReceiverTask.mRemoteToken
                         .toWindowContainerToken(), navigationBarInsetsProviderRect,
-                new int[]{ITYPE_LOCAL_NAVIGATION_BAR_1});
+                new int[]{ITYPE_TOP_GENERIC_OVERLAY});
         mWm.mAtmService.mWindowOrganizerController.applyTransaction(wct);
 
         assertThat(navigationBarInsetsReceiverTask.mLocalInsetsSourceProviders
-                .valueAt(0).getSource().getType()).isEqualTo(ITYPE_LOCAL_NAVIGATION_BAR_1);
+                .valueAt(0).getSource().getType()).isEqualTo(ITYPE_TOP_GENERIC_OVERLAY);
     }
 
     @Test
@@ -799,12 +799,12 @@
         final WindowContainerTransaction wct = new WindowContainerTransaction();
         wct.addRectInsetsProvider(navigationBarInsetsReceiverTask.mRemoteToken
                         .toWindowContainerToken(), navigationBarInsetsProviderRect,
-                new int[]{ITYPE_LOCAL_NAVIGATION_BAR_1});
+                new int[]{ITYPE_TOP_GENERIC_OVERLAY});
         mWm.mAtmService.mWindowOrganizerController.applyTransaction(wct);
 
         final WindowContainerTransaction wct2 = new WindowContainerTransaction();
         wct2.removeInsetsProvider(navigationBarInsetsReceiverTask.mRemoteToken
-                .toWindowContainerToken(), new int[]{ITYPE_LOCAL_NAVIGATION_BAR_1});
+                .toWindowContainerToken(), new int[]{ITYPE_TOP_GENERIC_OVERLAY});
         mWm.mAtmService.mWindowOrganizerController.applyTransaction(wct2);
 
         assertThat(navigationBarInsetsReceiverTask.mLocalInsetsSourceProviders.size()).isEqualTo(0);