Merge "Collect tasks when windowing mode of parent display changes" 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/CameraCharacteristics.java b/core/java/android/hardware/camera2/CameraCharacteristics.java
index dac55ae..8db298f 100644
--- a/core/java/android/hardware/camera2/CameraCharacteristics.java
+++ b/core/java/android/hardware/camera2/CameraCharacteristics.java
@@ -1325,7 +1325,8 @@
      * flashlight brightness level via
      * {@link android.hardware.camera2.CameraManager#turnOnTorchWithStrengthLevel }.
      * If this value is equal to 1, flashlight brightness control is not supported.
-     * The value for this key will be null for devices with no flash unit.</p>
+     * The value for this key will be null for devices with no flash unit.
+     * This level must be set to a safe value to prevent any burn out issues.</p>
      * <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p>
      */
     @PublicKey
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/IRecentsAnimationController.aidl b/core/java/android/view/IRecentsAnimationController.aidl
index 61f524f..c4d3070 100644
--- a/core/java/android/view/IRecentsAnimationController.aidl
+++ b/core/java/android/view/IRecentsAnimationController.aidl
@@ -80,11 +80,6 @@
     void setAnimationTargetsBehindSystemBars(boolean behindSystemBars);
 
     /**
-     * Hides the current input method if one is showing.
-     */
-    void hideCurrentInputMethod();
-
-    /**
      * Clean up the screenshot of previous task which was created during recents animation that
      * was cancelled by a stack order change.
      *
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 8ec32a6..7be8dff 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -3759,6 +3759,7 @@
             }
         }
         mFirstInputStage.onWindowFocusChanged(hasWindowFocus);
+        mOnBackInvokedDispatcher.onWindowFocusChanged(hasWindowFocus);
 
         // NOTE: there's no view visibility (appeared / disapparead) events when the windows focus
         // is lost, so we don't need to to force a flush - there might be other events such as
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index 23393ff..2268bef 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -6440,9 +6440,8 @@
     public void setText(CharSequence text, BufferType type) {
         setText(text, type, true, 0);
 
-        if (mCharWrapper != null) {
-            mCharWrapper.mChars = null;
-        }
+        // drop any potential mCharWrappper leaks
+        mCharWrapper = null;
     }
 
     @UnsupportedAppUsage
@@ -6653,11 +6652,14 @@
      * since the TextView has no way to know that the text
      * has changed and that it needs to invalidate and re-layout.
      *
+     * @throws NullPointerException if text is null
+     * @throws IndexOutOfBoundsException if start or start+len are not in 0 to text.length
+     *
      * @param text char array to be displayed
      * @param start start index in the char array
      * @param len length of char count after {@code start}
      */
-    public final void setText(char[] text, int start, int len) {
+    public final void setText(/* @NonNull */ char[] text, int start, int len) {
         int oldlen = 0;
 
         if (start < 0 || len < 0 || start + len > text.length) {
@@ -13888,16 +13890,17 @@
     }
 
     private static class CharWrapper implements CharSequence, GetChars, GraphicsOperations {
+        @NonNull
         private char[] mChars;
         private int mStart, mLength;
 
-        public CharWrapper(char[] chars, int start, int len) {
+        CharWrapper(@NonNull char[] chars, int start, int len) {
             mChars = chars;
             mStart = start;
             mLength = len;
         }
 
-        /* package */ void set(char[] chars, int start, int len) {
+        /* package */ void set(@NonNull char[] chars, int start, int len) {
             mChars = chars;
             mStart = start;
             mLength = len;
diff --git a/core/java/android/window/ImeOnBackInvokedDispatcher.java b/core/java/android/window/ImeOnBackInvokedDispatcher.java
index 5924844..f1a052b 100644
--- a/core/java/android/window/ImeOnBackInvokedDispatcher.java
+++ b/core/java/android/window/ImeOnBackInvokedDispatcher.java
@@ -81,8 +81,10 @@
             @OnBackInvokedDispatcher.Priority int priority,
             @NonNull OnBackInvokedCallback callback) {
         final Bundle bundle = new Bundle();
+        // Always invoke back for ime without checking the window focus.
         final IOnBackInvokedCallback iCallback =
-                new WindowOnBackInvokedDispatcher.OnBackInvokedCallbackWrapper(callback);
+                new WindowOnBackInvokedDispatcher.OnBackInvokedCallbackWrapper(callback,
+                        () -> true);
         bundle.putBinder(RESULT_KEY_CALLBACK, iCallback.asBinder());
         bundle.putInt(RESULT_KEY_PRIORITY, priority);
         bundle.putInt(RESULT_KEY_ID, callback.hashCode());
diff --git a/core/java/android/window/WindowOnBackInvokedDispatcher.java b/core/java/android/window/WindowOnBackInvokedDispatcher.java
index d147524d..02c5ebc 100644
--- a/core/java/android/window/WindowOnBackInvokedDispatcher.java
+++ b/core/java/android/window/WindowOnBackInvokedDispatcher.java
@@ -32,6 +32,7 @@
 import java.util.HashMap;
 import java.util.Objects;
 import java.util.TreeMap;
+import java.util.function.Supplier;
 
 /**
  * Provides window based implementation of {@link OnBackInvokedDispatcher}.
@@ -64,6 +65,7 @@
     private final TreeMap<Integer, ArrayList<OnBackInvokedCallback>>
             mOnBackInvokedCallbacks = new TreeMap<>();
     private final Checker mChecker;
+    private boolean mHasFocus;
 
     public WindowOnBackInvokedDispatcher(boolean applicationCallBackEnabled) {
         mChecker = new Checker(applicationCallBackEnabled);
@@ -189,7 +191,7 @@
                                     .ImeOnBackInvokedCallback
                                 ? ((ImeOnBackInvokedDispatcher.ImeOnBackInvokedCallback)
                                         callback).getIOnBackInvokedCallback()
-                                : new OnBackInvokedCallbackWrapper(callback);
+                                : new OnBackInvokedCallbackWrapper(callback, this::hasFocus);
                 callbackInfo = new OnBackInvokedCallbackInfo(iCallback, priority);
             }
             mWindowSession.setOnBackInvokedCallbackInfo(mWindow, callbackInfo);
@@ -198,6 +200,17 @@
         }
     }
 
+    /**
+     * Called when window focus changed.
+     */
+    public void onWindowFocusChanged(boolean hasFocus) {
+        mHasFocus = hasFocus;
+    }
+
+    private boolean hasFocus() {
+        return mHasFocus;
+    }
+
     public OnBackInvokedCallback getTopCallback() {
         if (mAllCallbacks.isEmpty()) {
             return null;
@@ -221,9 +234,11 @@
 
     static class OnBackInvokedCallbackWrapper extends IOnBackInvokedCallback.Stub {
         private final WeakReference<OnBackInvokedCallback> mCallback;
-
-        OnBackInvokedCallbackWrapper(@NonNull OnBackInvokedCallback callback) {
+        private final Supplier<Boolean> mHasFocus;
+        OnBackInvokedCallbackWrapper(@NonNull OnBackInvokedCallback callback,
+                @NonNull Supplier<Boolean> hasFocus) {
             mCallback = new WeakReference<>(callback);
+            mHasFocus = hasFocus;
         }
 
         @Override
@@ -263,7 +278,10 @@
                 if (callback == null) {
                     return;
                 }
-
+                if (!mHasFocus.get()) {
+                    Log.w(TAG, "Skip back invoke due to current focus has lost.");
+                    return;
+                }
                 callback.onBackInvoked();
             });
         }
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/core/tests/coretests/src/android/widget/TextViewTest.java b/core/tests/coretests/src/android/widget/TextViewTest.java
index 47ce2d8..cc4fbab 100644
--- a/core/tests/coretests/src/android/widget/TextViewTest.java
+++ b/core/tests/coretests/src/android/widget/TextViewTest.java
@@ -304,6 +304,23 @@
         assertFalse(mTextView.isCursorVisible());
     }
 
+    @Test(expected = NullPointerException.class)
+    @UiThreadTest
+    public void setTextCharArrayNullThrows() {
+        mTextView = new TextView(mActivity);
+        mTextView.setText((char[]) null, 0, 0);
+    }
+
+    @Test
+    @UiThreadTest
+    public void setTextCharArrayValidAfterSetTextString() {
+        mTextView = new TextView(mActivity);
+        mTextView.setText(new char[] { 'h', 'i'}, 0, 2);
+        CharSequence charWrapper = mTextView.getText();
+        mTextView.setText("null out char wrapper");
+        assertEquals("hi", charWrapper.toString());
+    }
+
     private String createLongText() {
         int size = 600 * 1000;
         final StringBuilder builder = new StringBuilder(size);
diff --git a/core/tests/coretests/src/android/window/WindowOnBackInvokedDispatcherTest.java b/core/tests/coretests/src/android/window/WindowOnBackInvokedDispatcherTest.java
index f448cb3..b5194f6 100644
--- a/core/tests/coretests/src/android/window/WindowOnBackInvokedDispatcherTest.java
+++ b/core/tests/coretests/src/android/window/WindowOnBackInvokedDispatcherTest.java
@@ -66,6 +66,7 @@
         MockitoAnnotations.initMocks(this);
         mDispatcher = new WindowOnBackInvokedDispatcher(true /* applicationCallbackEnabled */);
         mDispatcher.attachToWindow(mWindowSession, mWindow);
+        mDispatcher.onWindowFocusChanged(true);
     }
 
     private void waitForIdle() {
@@ -152,4 +153,31 @@
         waitForIdle();
         verify(mCallback2).onBackStarted();
     }
+
+    @Test
+    public void skipBackInvokeWhenNoFocus() throws RemoteException {
+        ArgumentCaptor<OnBackInvokedCallbackInfo> captor =
+                ArgumentCaptor.forClass(OnBackInvokedCallbackInfo.class);
+
+        mDispatcher.registerOnBackInvokedCallback(
+                OnBackInvokedDispatcher.PRIORITY_DEFAULT, mCallback1);
+
+        verify(mWindowSession, times(1)).setOnBackInvokedCallbackInfo(
+                Mockito.eq(mWindow),
+                captor.capture());
+
+        verify(mWindowSession).setOnBackInvokedCallbackInfo(Mockito.eq(mWindow), captor.capture());
+
+        // Should invoke back if it's still in focused.
+        captor.getValue().getCallback().onBackInvoked();
+        waitForIdle();
+        verify(mCallback1).onBackInvoked();
+
+        // In case the focus has lost during back gesture.
+        mDispatcher.onWindowFocusChanged(false);
+
+        captor.getValue().getCallback().onBackInvoked();
+        waitForIdle();
+        verifyZeroInteractions(mCallback1);
+    }
 }
diff --git a/data/fonts/fonts.xml b/data/fonts/fonts.xml
index 84e949a..f8c015f 100644
--- a/data/fonts/fonts.xml
+++ b/data/fonts/fonts.xml
@@ -932,7 +932,7 @@
         <font weight="700" style="normal" fallbackFor="serif">NotoSerifLao-Bold.ttf</font>
     </family>
     <family lang="und-Laoo" variant="compact">
-        <font weight="400" style="normal">NotoSansLaoUI-Regular.ttf
+        <font weight="400" style="normal" postScriptName="NotoSansLaoUI">NotoSansLaoUI-Regular.ttf
         </font>
         <font weight="700" style="normal">NotoSansLaoUI-Bold.ttf</font>
     </family>
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/back/BackAnimation.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimation.java
index 8c0affb..86f9d5b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimation.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimation.java
@@ -29,6 +29,13 @@
 public interface BackAnimation {
 
     /**
+     * Returns a binder that can be passed to an external process to update back animations.
+     */
+    default IBackAnimation createExternalInterface() {
+        return null;
+    }
+
+    /**
      * Called when a {@link MotionEvent} is generated by a back gesture.
      *
      * @param touchX the X touch position of the {@link MotionEvent}.
@@ -47,13 +54,6 @@
     void setTriggerBack(boolean triggerBack);
 
     /**
-     * Returns a binder that can be passed to an external process to update back animations.
-     */
-    default IBackAnimation createExternalInterface() {
-        return null;
-    }
-
-    /**
      * Sets the threshold values that defining edge swipe behavior.
      * @param triggerThreshold the min threshold to trigger back.
      * @param progressThreshold the max threshold to keep progressing back animation.
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/compatui/CompatUI.java b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUI.java
deleted file mode 100644
index b87cf47..0000000
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUI.java
+++ /dev/null
@@ -1,35 +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.wm.shell.compatui;
-
-import com.android.wm.shell.common.annotations.ExternalThread;
-
-/**
- * Interface to engage compat UI.
- */
-@ExternalThread
-public interface CompatUI {
-    /**
-     * Called when the keyguard showing state changes. Removes all compat UIs if the
-     * keyguard is now showing.
-     *
-     * <p>Note that if the keyguard is occluded it will also be considered showing.
-     *
-     * @param showing indicates if the keyguard is now showing.
-     */
-    void onKeyguardShowingChanged(boolean showing);
-}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIController.java
index 99b32a6..db8d9d4 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIController.java
@@ -39,9 +39,10 @@
 import com.android.wm.shell.common.DisplayLayout;
 import com.android.wm.shell.common.ShellExecutor;
 import com.android.wm.shell.common.SyncTransactionQueue;
-import com.android.wm.shell.common.annotations.ExternalThread;
 import com.android.wm.shell.compatui.CompatUIWindowManager.CompatUIHintsState;
 import com.android.wm.shell.compatui.letterboxedu.LetterboxEduWindowManager;
+import com.android.wm.shell.sysui.KeyguardChangeListener;
+import com.android.wm.shell.sysui.ShellController;
 import com.android.wm.shell.transition.Transitions;
 
 import java.lang.ref.WeakReference;
@@ -58,7 +59,7 @@
  * activities are in compatibility mode.
  */
 public class CompatUIController implements OnDisplaysChangedListener,
-        DisplayImeController.ImePositionProcessor {
+        DisplayImeController.ImePositionProcessor, KeyguardChangeListener {
 
     /** Callback for compat UI interaction. */
     public interface CompatUICallback {
@@ -100,13 +101,13 @@
     private final SparseArray<WeakReference<Context>> mDisplayContextCache = new SparseArray<>(0);
 
     private final Context mContext;
+    private final ShellController mShellController;
     private final DisplayController mDisplayController;
     private final DisplayInsetsController mDisplayInsetsController;
     private final DisplayImeController mImeController;
     private final SyncTransactionQueue mSyncQueue;
     private final ShellExecutor mMainExecutor;
     private final Lazy<Transitions> mTransitionsLazy;
-    private final CompatUIImpl mImpl = new CompatUIImpl();
 
     private CompatUICallback mCallback;
 
@@ -118,6 +119,7 @@
     private boolean mKeyguardShowing;
 
     public CompatUIController(Context context,
+            ShellController shellController,
             DisplayController displayController,
             DisplayInsetsController displayInsetsController,
             DisplayImeController imeController,
@@ -125,6 +127,7 @@
             ShellExecutor mainExecutor,
             Lazy<Transitions> transitionsLazy) {
         mContext = context;
+        mShellController = shellController;
         mDisplayController = displayController;
         mDisplayInsetsController = displayInsetsController;
         mImeController = imeController;
@@ -134,11 +137,7 @@
         mDisplayController.addDisplayWindowListener(this);
         mImeController.addPositionProcessor(this);
         mCompatUIHintsState = new CompatUIHintsState();
-    }
-
-    /** Returns implementation of {@link CompatUI}. */
-    public CompatUI asCompatUI() {
-        return mImpl;
+        shellController.addKeyguardChangeListener(this);
     }
 
     /** Sets the callback for UI interactions. */
@@ -223,9 +222,10 @@
                 layout -> layout.updateVisibility(showOnDisplay(displayId)));
     }
 
-    @VisibleForTesting
-    void onKeyguardShowingChanged(boolean showing) {
-        mKeyguardShowing = showing;
+    @Override
+    public void onKeyguardVisibilityChanged(boolean visible, boolean occluded,
+            boolean animatingDismiss) {
+        mKeyguardShowing = visible;
         // Hide the compat UIs when keyguard is showing.
         forAllLayouts(layout -> layout.updateVisibility(showOnDisplay(layout.getDisplayId())));
     }
@@ -373,19 +373,6 @@
         }
     }
 
-    /**
-     * The interface for calls from outside the Shell, within the host process.
-     */
-    @ExternalThread
-    private class CompatUIImpl implements CompatUI {
-        @Override
-        public void onKeyguardShowingChanged(boolean showing) {
-            mMainExecutor.execute(() -> {
-                CompatUIController.this.onKeyguardShowingChanged(showing);
-            });
-        }
-    }
-
     /** An implementation of {@link OnInsetsChangedListener} for a given display id. */
     private class PerDisplayOnInsetsChangedListener implements OnInsetsChangedListener {
         final int mDisplayId;
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 ed5e247..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;
@@ -57,15 +55,12 @@
 import com.android.wm.shell.common.annotations.ShellBackgroundThread;
 import com.android.wm.shell.common.annotations.ShellMainThread;
 import com.android.wm.shell.common.annotations.ShellSplashscreenThread;
-import com.android.wm.shell.compatui.CompatUI;
 import com.android.wm.shell.compatui.CompatUIController;
 import com.android.wm.shell.displayareahelper.DisplayAreaHelper;
 import com.android.wm.shell.displayareahelper.DisplayAreaHelperController;
-import com.android.wm.shell.draganddrop.DragAndDrop;
 import com.android.wm.shell.draganddrop.DragAndDropController;
 import com.android.wm.shell.freeform.FreeformTaskListener;
 import com.android.wm.shell.fullscreen.FullscreenTaskListener;
-import com.android.wm.shell.hidedisplaycutout.HideDisplayCutout;
 import com.android.wm.shell.hidedisplaycutout.HideDisplayCutoutController;
 import com.android.wm.shell.kidsmode.KidsModeTaskOrganizer;
 import com.android.wm.shell.onehanded.OneHanded;
@@ -85,8 +80,6 @@
 import com.android.wm.shell.startingsurface.phone.PhoneStartingWindowTypeAlgorithm;
 import com.android.wm.shell.sysui.ShellController;
 import com.android.wm.shell.sysui.ShellInterface;
-import com.android.wm.shell.tasksurfacehelper.TaskSurfaceHelper;
-import com.android.wm.shell.tasksurfacehelper.TaskSurfaceHelperController;
 import com.android.wm.shell.transition.ShellTransitions;
 import com.android.wm.shell.transition.Transitions;
 import com.android.wm.shell.unfold.ShellUnfoldProgressProvider;
@@ -173,12 +166,6 @@
 
     @WMSingleton
     @Provides
-    static Optional<DragAndDrop> provideDragAndDrop(DragAndDropController dragAndDropController) {
-        return Optional.of(dragAndDropController.asDragAndDrop());
-    }
-
-    @WMSingleton
-    @Provides
     static ShellTaskOrganizer provideShellTaskOrganizer(@ShellMainThread ShellExecutor mainExecutor,
             Context context,
             CompatUIController compatUI,
@@ -207,18 +194,14 @@
     }
 
     @WMSingleton
-    @Provides static Optional<CompatUI> provideCompatUI(CompatUIController compatUIController) {
-        return Optional.of(compatUIController.asCompatUI());
-    }
-
-    @WMSingleton
     @Provides
     static CompatUIController provideCompatUIController(Context context,
+            ShellController shellController,
             DisplayController displayController, DisplayInsetsController displayInsetsController,
             DisplayImeController imeController, SyncTransactionQueue syncQueue,
             @ShellMainThread ShellExecutor mainExecutor, Lazy<Transitions> transitionsLazy) {
-        return new CompatUIController(context, displayController, displayInsetsController,
-                imeController, syncQueue, mainExecutor, transitionsLazy);
+        return new CompatUIController(context, shellController, displayController,
+                displayInsetsController, imeController, syncQueue, mainExecutor, transitionsLazy);
     }
 
     @WMSingleton
@@ -375,13 +358,6 @@
 
     @WMSingleton
     @Provides
-    static Optional<HideDisplayCutout> provideHideDisplayCutout(
-            Optional<HideDisplayCutoutController> hideDisplayCutoutController) {
-        return hideDisplayCutoutController.map((controller) -> controller.asHideDisplayCutout());
-    }
-
-    @WMSingleton
-    @Provides
     static Optional<HideDisplayCutoutController> provideHideDisplayCutoutController(Context context,
             ShellController shellController, DisplayController displayController,
             @ShellMainThread ShellExecutor mainExecutor) {
@@ -416,23 +392,6 @@
     }
 
     //
-    // Task to Surface communication
-    //
-
-    @WMSingleton
-    @Provides
-    static Optional<TaskSurfaceHelper> provideTaskSurfaceHelper(
-            Optional<TaskSurfaceHelperController> taskSurfaceController) {
-        return taskSurfaceController.map((controller) -> controller.asTaskSurfaceHelper());
-    }
-
-    @Provides
-    static Optional<TaskSurfaceHelperController> provideTaskSurfaceHelperController(
-            ShellTaskOrganizer taskOrganizer, @ShellMainThread ShellExecutor mainExecutor) {
-        return Optional.ofNullable(new TaskSurfaceHelperController(taskOrganizer, mainExecutor));
-    }
-
-    //
     // Pip (optional feature)
     //
 
@@ -663,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,
@@ -687,7 +642,8 @@
             Transitions transitions,
             StartingWindowController startingWindow,
             @ShellMainThread ShellExecutor mainExecutor) {
-        return new ShellInitImpl(displayController,
+        return new ShellInit(shellController,
+                displayController,
                 displayImeController,
                 displayInsetsController,
                 dragAndDropController,
@@ -707,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,
@@ -728,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/dagger/WMShellModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
index fb51473..0f33f4c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
@@ -225,6 +225,7 @@
     @Provides
     @DynamicOverride
     static SplitScreenController provideSplitScreenController(
+            ShellController shellController,
             ShellTaskOrganizer shellTaskOrganizer,
             SyncTransactionQueue syncQueue, Context context,
             RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer,
@@ -234,7 +235,7 @@
             DisplayInsetsController displayInsetsController, Transitions transitions,
             TransactionPool transactionPool, IconProvider iconProvider,
             Optional<RecentTasksController> recentTasks) {
-        return new SplitScreenController(shellTaskOrganizer, syncQueue, context,
+        return new SplitScreenController(shellController, shellTaskOrganizer, syncQueue, context,
                 rootTaskDisplayAreaOrganizer, mainExecutor, displayController, displayImeController,
                 displayInsetsController, transitions, transactionPool, iconProvider,
                 recentTasks);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDrop.java b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDrop.java
deleted file mode 100644
index 0335187..0000000
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDrop.java
+++ /dev/null
@@ -1,26 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.wm.shell.draganddrop;
-
-import com.android.wm.shell.common.annotations.ExternalThread;
-
-/**
- * Interface for telling DragAndDrop stuff.
- */
-@ExternalThread
-public interface DragAndDrop {
-}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropController.java
index 0d0961c..c5df53b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropController.java
@@ -81,11 +81,9 @@
     private final IconProvider mIconProvider;
     private SplitScreenController mSplitScreen;
     private ShellExecutor mMainExecutor;
-    private DragAndDropImpl mImpl;
     private ArrayList<DragAndDropListener> mListeners = new ArrayList<>();
 
     private final SparseArray<PerDisplay> mDisplayDropTargets = new SparseArray<>();
-    private final SurfaceControl.Transaction mTransaction = new SurfaceControl.Transaction();
 
     /**
      * Listener called during drag events, currently just onDragStarted.
@@ -107,11 +105,6 @@
         mLogger = new DragAndDropEventLogger(uiEventLogger);
         mIconProvider = iconProvider;
         mMainExecutor = mainExecutor;
-        mImpl = new DragAndDropImpl();
-    }
-
-    public DragAndDrop asDragAndDrop() {
-        return mImpl;
     }
 
     public void initialize(Optional<SplitScreenController> splitscreen) {
@@ -353,8 +346,4 @@
             dragLayout = dl;
         }
     }
-
-    private class DragAndDropImpl implements DragAndDrop {
-        // TODO: To be removed
-    }
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/hidedisplaycutout/HideDisplayCutout.java b/libs/WindowManager/Shell/src/com/android/wm/shell/hidedisplaycutout/HideDisplayCutout.java
deleted file mode 100644
index dd1c8d6..0000000
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/hidedisplaycutout/HideDisplayCutout.java
+++ /dev/null
@@ -1,26 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.wm.shell.hidedisplaycutout;
-
-import com.android.wm.shell.common.annotations.ExternalThread;
-
-/**
- * Interface to engage hide display cutout feature.
- */
-@ExternalThread
-public interface HideDisplayCutout {
-}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/hidedisplaycutout/HideDisplayCutoutController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/hidedisplaycutout/HideDisplayCutoutController.java
index b091ab8..665b035 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/hidedisplaycutout/HideDisplayCutoutController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/hidedisplaycutout/HideDisplayCutoutController.java
@@ -40,8 +40,6 @@
     private final Context mContext;
     private final ShellController mShellController;
     private final HideDisplayCutoutOrganizer mOrganizer;
-    private final ShellExecutor mMainExecutor;
-    private final HideDisplayCutoutImpl mImpl = new HideDisplayCutoutImpl();
     @VisibleForTesting
     boolean mEnabled;
 
@@ -62,23 +60,18 @@
 
         HideDisplayCutoutOrganizer organizer =
                 new HideDisplayCutoutOrganizer(context, displayController, mainExecutor);
-        return new HideDisplayCutoutController(context, shellController, organizer, mainExecutor);
+        return new HideDisplayCutoutController(context, shellController, organizer);
     }
 
     HideDisplayCutoutController(Context context, ShellController shellController,
-            HideDisplayCutoutOrganizer organizer, ShellExecutor mainExecutor) {
+            HideDisplayCutoutOrganizer organizer) {
         mContext = context;
         mShellController = shellController;
         mOrganizer = organizer;
-        mMainExecutor = mainExecutor;
         updateStatus();
         mShellController.addConfigurationChangeListener(this);
     }
 
-    public HideDisplayCutout asHideDisplayCutout() {
-        return mImpl;
-    }
-
     @VisibleForTesting
     void updateStatus() {
         // The config value is used for controlling enabling/disabling status of the feature and is
@@ -112,8 +105,4 @@
         pw.println(mEnabled);
         mOrganizer.dump(pw);
     }
-
-    private class HideDisplayCutoutImpl implements HideDisplayCutout {
-        // TODO: To be removed
-    }
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHanded.java b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHanded.java
index ee99f327..76c0f41 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHanded.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHanded.java
@@ -85,9 +85,4 @@
      * Notifies when user switch complete
      */
     void onUserSwitch(int userId);
-
-    /**
-     * Notifies when keyguard visibility changed
-     */
-    void onKeyguardVisibilityChanged(boolean showing);
 }
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 ea2d508..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
@@ -55,6 +55,7 @@
 import com.android.wm.shell.common.TaskStackListenerImpl;
 import com.android.wm.shell.common.annotations.ExternalThread;
 import com.android.wm.shell.sysui.ConfigurationChangeListener;
+import com.android.wm.shell.sysui.KeyguardChangeListener;
 import com.android.wm.shell.sysui.ShellController;
 
 import java.io.PrintWriter;
@@ -63,7 +64,8 @@
  * Manages and manipulates the one handed states, transitions, and gesture for phones.
  */
 public class OneHandedController implements RemoteCallable<OneHandedController>,
-        DisplayChangeController.OnDisplayChangingListener, ConfigurationChangeListener {
+        DisplayChangeController.OnDisplayChangingListener, ConfigurationChangeListener,
+        KeyguardChangeListener {
     private static final String TAG = "OneHandedController";
 
     private static final String ONE_HANDED_MODE_OFFSET_PERCENTAGE =
@@ -279,6 +281,7 @@
 
         mState.addSListeners(mTutorialHandler);
         mShellController.addConfigurationChangeListener(this);
+        mShellController.addKeyguardChangeListener(this);
     }
 
     public OneHanded asOneHanded() {
@@ -605,8 +608,11 @@
         mTutorialHandler.onConfigurationChanged();
     }
 
-    private void onKeyguardVisibilityChanged(boolean showing) {
-        mKeyguardShowing = showing;
+    @Override
+    public void onKeyguardVisibilityChanged(boolean visible, boolean occluded,
+            boolean animatingDismiss) {
+        mKeyguardShowing = visible;
+        stopOneHanded();
     }
 
     private void onUserSwitch(int newUserId) {
@@ -675,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);
     }
 
     /**
@@ -756,13 +765,6 @@
                 OneHandedController.this.onUserSwitch(userId);
             });
         }
-
-        @Override
-        public void onKeyguardVisibilityChanged(boolean showing) {
-            mMainExecutor.execute(() -> {
-                OneHandedController.this.onKeyguardVisibilityChanged(showing);
-            });
-        }
     }
 
     /**
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/Pip.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/Pip.java
index 2ac1129..38631ce 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/Pip.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/Pip.java
@@ -101,23 +101,6 @@
     default void removePipExclusionBoundsChangeListener(Consumer<Rect> listener) { }
 
     /**
-     * Called when the visibility of keyguard is changed.
-     * @param showing {@code true} if keyguard is now showing, {@code false} otherwise.
-     * @param animating {@code true} if system is animating between keyguard and surface behind,
-     *                              this only makes sense when showing is {@code false}.
-     */
-    default void onKeyguardVisibilityChanged(boolean showing, boolean animating) { }
-
-    /**
-     * Called when the dismissing animation keyguard and surfaces behind is finished.
-     * See also {@link #onKeyguardVisibilityChanged(boolean, boolean)}.
-     *
-     * TODO(b/206741900) deprecate this path once we're able to animate the PiP window as part of
-     * keyguard dismiss animation.
-     */
-    default void onKeyguardDismissAnimationFinished() { }
-
-    /**
      * Dump the current state and information if need.
      *
      * @param pw The stream to dump information to.
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
index 5ae4602..420d606 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
@@ -88,6 +88,7 @@
 import com.android.wm.shell.pip.PipUtils;
 import com.android.wm.shell.protolog.ShellProtoLogGroup;
 import com.android.wm.shell.sysui.ConfigurationChangeListener;
+import com.android.wm.shell.sysui.KeyguardChangeListener;
 import com.android.wm.shell.sysui.ShellController;
 import com.android.wm.shell.transition.Transitions;
 
@@ -102,7 +103,7 @@
  * Manages the picture-in-picture (PIP) UI and states for Phones.
  */
 public class PipController implements PipTransitionController.PipTransitionCallback,
-        RemoteCallable<PipController>, ConfigurationChangeListener {
+        RemoteCallable<PipController>, ConfigurationChangeListener, KeyguardChangeListener {
     private static final String TAG = "PipController";
 
     private Context mContext;
@@ -527,6 +528,7 @@
         });
 
         mShellController.addConfigurationChangeListener(this);
+        mShellController.addKeyguardChangeListener(this);
     }
 
     @Override
@@ -661,21 +663,24 @@
      * finished first to reset the visibility of PiP window.
      * See also {@link #onKeyguardDismissAnimationFinished()}
      */
-    private void onKeyguardVisibilityChanged(boolean keyguardShowing, boolean animating) {
+    @Override
+    public void onKeyguardVisibilityChanged(boolean visible, boolean occluded,
+            boolean animatingDismiss) {
         if (!mPipTaskOrganizer.isInPip()) {
             return;
         }
-        if (keyguardShowing) {
+        if (visible) {
             mIsKeyguardShowingOrAnimating = true;
             hidePipMenu(null /* onStartCallback */, null /* onEndCallback */);
             mPipTaskOrganizer.setPipVisibility(false);
-        } else if (!animating) {
+        } else if (!animatingDismiss) {
             mIsKeyguardShowingOrAnimating = false;
             mPipTaskOrganizer.setPipVisibility(true);
         }
     }
 
-    private void onKeyguardDismissAnimationFinished() {
+    @Override
+    public void onKeyguardDismissAnimationFinished() {
         if (mPipTaskOrganizer.isInPip()) {
             mIsKeyguardShowingOrAnimating = false;
             mPipTaskOrganizer.setPipVisibility(true);
@@ -996,18 +1001,6 @@
         }
 
         @Override
-        public void onKeyguardVisibilityChanged(boolean showing, boolean animating) {
-            mMainExecutor.execute(() -> {
-                PipController.this.onKeyguardVisibilityChanged(showing, animating);
-            });
-        }
-
-        @Override
-        public void onKeyguardDismissAnimationFinished() {
-            mMainExecutor.execute(PipController.this::onKeyguardDismissAnimationFinished);
-        }
-
-        @Override
         public void dump(PrintWriter pw) {
             try {
                 mMainExecutor.executeBlocking(() -> {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreen.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreen.java
index 29b6311..e73b799 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreen.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreen.java
@@ -77,12 +77,6 @@
         return null;
     }
 
-    /**
-     * Called when the visibility of the keyguard changes.
-     * @param showing Indicates if the keyguard is now visible.
-     */
-    void onKeyguardVisibilityChanged(boolean showing);
-
     /** Called when device waking up finished. */
     void onFinishedWakingUp();
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
index 21bea46..53ec39d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
@@ -80,6 +80,8 @@
 import com.android.wm.shell.protolog.ShellProtoLogGroup;
 import com.android.wm.shell.recents.RecentTasksController;
 import com.android.wm.shell.splitscreen.SplitScreen.StageType;
+import com.android.wm.shell.sysui.KeyguardChangeListener;
+import com.android.wm.shell.sysui.ShellController;
 import com.android.wm.shell.transition.LegacyTransitions;
 import com.android.wm.shell.transition.Transitions;
 
@@ -99,7 +101,7 @@
  */
 // TODO(b/198577848): Implement split screen flicker test to consolidate CUJ of split screen.
 public class SplitScreenController implements DragAndDropPolicy.Starter,
-        RemoteCallable<SplitScreenController> {
+        RemoteCallable<SplitScreenController>, KeyguardChangeListener {
     private static final String TAG = SplitScreenController.class.getSimpleName();
 
     public static final int EXIT_REASON_UNKNOWN = 0;
@@ -127,6 +129,7 @@
     @Retention(RetentionPolicy.SOURCE)
     @interface ExitReason{}
 
+    private final ShellController mShellController;
     private final ShellTaskOrganizer mTaskOrganizer;
     private final SyncTransactionQueue mSyncQueue;
     private final Context mContext;
@@ -147,7 +150,8 @@
     // outside the bounds of the roots by being reparented into a higher level fullscreen container
     private SurfaceControl mSplitTasksContainerLayer;
 
-    public SplitScreenController(ShellTaskOrganizer shellTaskOrganizer,
+    public SplitScreenController(ShellController shellController,
+            ShellTaskOrganizer shellTaskOrganizer,
             SyncTransactionQueue syncQueue, Context context,
             RootTaskDisplayAreaOrganizer rootTDAOrganizer,
             ShellExecutor mainExecutor, DisplayController displayController,
@@ -155,6 +159,7 @@
             DisplayInsetsController displayInsetsController,
             Transitions transitions, TransactionPool transactionPool, IconProvider iconProvider,
             Optional<RecentTasksController> recentTasks) {
+        mShellController = shellController;
         mTaskOrganizer = shellTaskOrganizer;
         mSyncQueue = syncQueue;
         mContext = context;
@@ -185,6 +190,7 @@
     }
 
     public void onOrganizerRegistered() {
+        mShellController.addKeyguardChangeListener(this);
         if (mStageCoordinator == null) {
             // TODO: Multi-display
             mStageCoordinator = new StageCoordinator(mContext, DEFAULT_DISPLAY, mSyncQueue,
@@ -283,8 +289,10 @@
         mStageCoordinator.exitSplitScreen(toTopTaskId, exitReason);
     }
 
-    public void onKeyguardVisibilityChanged(boolean showing) {
-        mStageCoordinator.onKeyguardVisibilityChanged(showing);
+    @Override
+    public void onKeyguardVisibilityChanged(boolean visible, boolean occluded,
+            boolean animatingDismiss) {
+        mStageCoordinator.onKeyguardVisibilityChanged(visible);
     }
 
     public void onFinishedWakingUp() {
@@ -658,13 +666,6 @@
         }
 
         @Override
-        public void onKeyguardVisibilityChanged(boolean showing) {
-            mMainExecutor.execute(() -> {
-                SplitScreenController.this.onKeyguardVisibilityChanged(showing);
-            });
-        }
-
-        @Override
         public void onFinishedWakingUp() {
             mMainExecutor.execute(() -> {
                 SplitScreenController.this.onFinishedWakingUp();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/KeyguardChangeListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/KeyguardChangeListener.java
new file mode 100644
index 0000000..1c0b358
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/KeyguardChangeListener.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.sysui;
+
+/**
+ * Callbacks for when the keyguard changes.
+ */
+public interface KeyguardChangeListener {
+    /**
+     * Notifies the Shell that the keyguard is showing (and if so, whether it is occluded).
+     */
+    default void onKeyguardVisibilityChanged(boolean visible, boolean occluded,
+            boolean animatingDismiss) {}
+
+    /**
+     * Notifies the Shell when the keyguard dismiss animation has finished.
+     *
+     * TODO(b/206741900) deprecate this path once we're able to animate the PiP window as part of
+     * keyguard dismiss animation.
+     */
+    default void onKeyguardDismissAnimationFinished() {}
+}
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 28a30f4..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,7 +47,12 @@
     private final ShellExecutor mMainExecutor;
     private final ShellInterfaceImpl mImpl = new ShellInterfaceImpl();
 
-    private final CopyOnWriteArrayList<ConfigurationChangeListener> mListeners =
+    private ShellInit mShellInit;
+    private ShellCommandHandler mShellCommandHandler;
+
+    private final CopyOnWriteArrayList<ConfigurationChangeListener> mConfigChangeListeners =
+            new CopyOnWriteArrayList<>();
+    private final CopyOnWriteArrayList<KeyguardChangeListener> mKeyguardChangeListeners =
             new CopyOnWriteArrayList<>();
     private Configuration mLastConfiguration;
 
@@ -64,19 +69,53 @@
     }
 
     /**
+     * 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.
      */
     public void addConfigurationChangeListener(ConfigurationChangeListener listener) {
-        mListeners.remove(listener);
-        mListeners.add(listener);
+        mConfigChangeListeners.remove(listener);
+        mConfigChangeListeners.add(listener);
     }
 
     /**
      * Removes an existing configuration listener.
      */
     public void removeConfigurationChangeListener(ConfigurationChangeListener listener) {
-        mListeners.remove(listener);
+        mConfigChangeListeners.remove(listener);
+    }
+
+    /**
+     * Adds a new Keyguard listener. The Keyguard change callbacks are not made in any
+     * particular order.
+     */
+    public void addKeyguardChangeListener(KeyguardChangeListener listener) {
+        mKeyguardChangeListeners.remove(listener);
+        mKeyguardChangeListeners.add(listener);
+    }
+
+    /**
+     * Removes an existing Keyguard listener.
+     */
+    public void removeKeyguardChangeListener(KeyguardChangeListener listener) {
+        mKeyguardChangeListeners.remove(listener);
     }
 
     @VisibleForTesting
@@ -102,7 +141,7 @@
 
         // Update the last configuration and call listeners
         mLastConfiguration.updateFrom(newConfig);
-        for (ConfigurationChangeListener listener : mListeners) {
+        for (ConfigurationChangeListener listener : mConfigChangeListeners) {
             listener.onConfigurationChanged(newConfig);
             if (densityFontScaleChanged) {
                 listener.onDensityOrFontScaleChanged();
@@ -119,11 +158,26 @@
         }
     }
 
+    @VisibleForTesting
+    void onKeyguardVisibilityChanged(boolean visible, boolean occluded, boolean animatingDismiss) {
+        for (KeyguardChangeListener listener : mKeyguardChangeListeners) {
+            listener.onKeyguardVisibilityChanged(visible, occluded, animatingDismiss);
+        }
+    }
+
+    @VisibleForTesting
+    void onKeyguardDismissAnimationFinished() {
+        for (KeyguardChangeListener listener : mKeyguardChangeListeners) {
+            listener.onKeyguardDismissAnimationFinished();
+        }
+    }
+
     public void dump(@NonNull PrintWriter pw, String prefix) {
         final String innerPrefix = prefix + "  ";
         pw.println(prefix + TAG);
-        pw.println(innerPrefix + "mListeners=" + mListeners.size());
+        pw.println(innerPrefix + "mConfigChangeListeners=" + mConfigChangeListeners.size());
         pw.println(innerPrefix + "mLastConfiguration=" + mLastConfiguration);
+        pw.println(innerPrefix + "mKeyguardChangeListeners=" + mKeyguardChangeListeners.size());
     }
 
     /**
@@ -131,10 +185,56 @@
      */
     @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(() ->
                     ShellController.this.onConfigurationChanged(newConfiguration));
         }
+
+        @Override
+        public void onKeyguardVisibilityChanged(boolean visible, boolean occluded,
+                boolean animatingDismiss) {
+            mMainExecutor.execute(() ->
+                    ShellController.this.onKeyguardVisibilityChanged(visible, occluded,
+                            animatingDismiss));
+        }
+
+        @Override
+        public void onKeyguardDismissAnimationFinished() {
+            mMainExecutor.execute(() ->
+                    ShellController.this.onKeyguardDismissAnimationFinished());
+        }
     }
 }
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 a2d072c..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,16 +18,45 @@
 
 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) {}
+
+    /**
+     * Notifies the Shell that the keyguard is showing (and if so, whether it is occluded) or not
+     * showing, and whether it is animating a dismiss.
+     */
+    default void onKeyguardVisibilityChanged(boolean visible, boolean occluded,
+            boolean animatingDismiss) {}
+
+    /**
+     * Notifies the Shell when the keyguard dismiss animation has finished.
+     */
+    default void onKeyguardDismissAnimationFinished() {}
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/tasksurfacehelper/TaskSurfaceHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/tasksurfacehelper/TaskSurfaceHelper.java
deleted file mode 100644
index ad9dda6..0000000
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/tasksurfacehelper/TaskSurfaceHelper.java
+++ /dev/null
@@ -1,37 +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.wm.shell.tasksurfacehelper;
-
-import android.app.ActivityManager.RunningTaskInfo;
-import android.graphics.Rect;
-import android.view.SurfaceControl;
-
-import java.util.concurrent.Executor;
-import java.util.function.Consumer;
-
-/**
- * Interface to communicate with a Task's SurfaceControl.
- */
-public interface TaskSurfaceHelper {
-
-    /** Sets the METADATA_GAME_MODE for the layer corresponding to the task **/
-    default void setGameModeForTask(int taskId, int gameMode) {}
-
-    /** Takes a screenshot for a task **/
-    default void screenshotTask(RunningTaskInfo taskInfo, Rect crop, Executor executor,
-            Consumer<SurfaceControl.ScreenshotHardwareBuffer> consumer) {}
-}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/tasksurfacehelper/TaskSurfaceHelperController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/tasksurfacehelper/TaskSurfaceHelperController.java
deleted file mode 100644
index 064d9d1..0000000
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/tasksurfacehelper/TaskSurfaceHelperController.java
+++ /dev/null
@@ -1,82 +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.wm.shell.tasksurfacehelper;
-
-import android.app.ActivityManager.RunningTaskInfo;
-import android.graphics.Rect;
-import android.view.SurfaceControl;
-
-import com.android.wm.shell.ShellTaskOrganizer;
-import com.android.wm.shell.common.ShellExecutor;
-
-import java.util.concurrent.Executor;
-import java.util.function.Consumer;
-
-/**
- * Intermediary controller that communicates with {@link ShellTaskOrganizer} to send commands
- * to SurfaceControl.
- */
-public class TaskSurfaceHelperController {
-
-    private final ShellTaskOrganizer mTaskOrganizer;
-    private final ShellExecutor mMainExecutor;
-    private final TaskSurfaceHelperImpl mImpl = new TaskSurfaceHelperImpl();
-
-    public TaskSurfaceHelperController(ShellTaskOrganizer taskOrganizer,
-            ShellExecutor mainExecutor) {
-        mTaskOrganizer = taskOrganizer;
-        mMainExecutor = mainExecutor;
-    }
-
-    public TaskSurfaceHelper asTaskSurfaceHelper() {
-        return mImpl;
-    }
-
-    /**
-     * Sends a Transaction to set the game mode metadata on the
-     * corresponding SurfaceControl
-     */
-    public void setGameModeForTask(int taskId, int gameMode) {
-        mTaskOrganizer.setSurfaceMetadata(taskId, SurfaceControl.METADATA_GAME_MODE, gameMode);
-    }
-
-    /**
-     * Take screenshot of the specified task.
-     */
-    public void screenshotTask(RunningTaskInfo taskInfo, Rect crop,
-            Consumer<SurfaceControl.ScreenshotHardwareBuffer> consumer) {
-        mTaskOrganizer.screenshotTask(taskInfo, crop, consumer);
-    }
-
-    private class TaskSurfaceHelperImpl implements TaskSurfaceHelper {
-        @Override
-        public void setGameModeForTask(int taskId, int gameMode) {
-            mMainExecutor.execute(() -> {
-                TaskSurfaceHelperController.this.setGameModeForTask(taskId, gameMode);
-            });
-        }
-
-        @Override
-        public void screenshotTask(RunningTaskInfo taskInfo, Rect crop, Executor executor,
-                Consumer<SurfaceControl.ScreenshotHardwareBuffer> consumer) {
-            mMainExecutor.execute(() -> {
-                TaskSurfaceHelperController.this.screenshotTask(taskInfo, crop,
-                        (t) -> executor.execute(() -> consumer.accept(t)));
-            });
-        }
-    }
-}
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/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIControllerTest.java
index 596100d..828c13e 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIControllerTest.java
@@ -53,6 +53,7 @@
 import com.android.wm.shell.common.ShellExecutor;
 import com.android.wm.shell.common.SyncTransactionQueue;
 import com.android.wm.shell.compatui.letterboxedu.LetterboxEduWindowManager;
+import com.android.wm.shell.sysui.ShellController;
 import com.android.wm.shell.transition.Transitions;
 
 import org.junit.Before;
@@ -78,6 +79,7 @@
     private static final int TASK_ID = 12;
 
     private CompatUIController mController;
+    private @Mock ShellController mMockShellController;
     private @Mock DisplayController mMockDisplayController;
     private @Mock DisplayInsetsController mMockDisplayInsetsController;
     private @Mock DisplayLayout mMockDisplayLayout;
@@ -105,7 +107,7 @@
         doReturn(TASK_ID).when(mMockLetterboxEduLayout).getTaskId();
         doReturn(true).when(mMockLetterboxEduLayout).createLayout(anyBoolean());
         doReturn(true).when(mMockLetterboxEduLayout).updateCompatInfo(any(), any(), anyBoolean());
-        mController = new CompatUIController(mContext, mMockDisplayController,
+        mController = new CompatUIController(mContext, mMockShellController, mMockDisplayController,
                 mMockDisplayInsetsController, mMockImeController, mMockSyncQueue, mMockExecutor,
                 mMockTransitionsLazy) {
             @Override
@@ -124,6 +126,11 @@
     }
 
     @Test
+    public void instantiateController_registerKeyguardChangeListener() {
+        verify(mMockShellController, times(1)).addKeyguardChangeListener(any());
+    }
+
+    @Test
     public void testListenerRegistered() {
         verify(mMockDisplayController).addDisplayWindowListener(mController);
         verify(mMockImeController).addPositionProcessor(mController);
@@ -324,7 +331,7 @@
                 /* hasSizeCompat= */ true, CAMERA_COMPAT_CONTROL_HIDDEN), mMockTaskListener);
 
         // Verify that the restart button is hidden after keyguard becomes showing.
-        mController.onKeyguardShowingChanged(true);
+        mController.onKeyguardVisibilityChanged(true, false, false);
 
         verify(mMockCompatLayout).updateVisibility(false);
         verify(mMockLetterboxEduLayout).updateVisibility(false);
@@ -340,7 +347,7 @@
                 false);
 
         // Verify button is shown after keyguard becomes not showing.
-        mController.onKeyguardShowingChanged(false);
+        mController.onKeyguardVisibilityChanged(false, false, false);
 
         verify(mMockCompatLayout).updateVisibility(true);
         verify(mMockLetterboxEduLayout).updateVisibility(true);
@@ -352,7 +359,7 @@
                 /* hasSizeCompat= */ true, CAMERA_COMPAT_CONTROL_HIDDEN), mMockTaskListener);
 
         mController.onImeVisibilityChanged(DISPLAY_ID, /* isShowing= */ true);
-        mController.onKeyguardShowingChanged(true);
+        mController.onKeyguardVisibilityChanged(true, false, false);
 
         verify(mMockCompatLayout, times(2)).updateVisibility(false);
         verify(mMockLetterboxEduLayout, times(2)).updateVisibility(false);
@@ -360,7 +367,7 @@
         clearInvocations(mMockCompatLayout, mMockLetterboxEduLayout);
 
         // Verify button remains hidden after keyguard becomes not showing since IME is showing.
-        mController.onKeyguardShowingChanged(false);
+        mController.onKeyguardVisibilityChanged(false, false, false);
 
         verify(mMockCompatLayout).updateVisibility(false);
         verify(mMockLetterboxEduLayout).updateVisibility(false);
@@ -378,7 +385,7 @@
                 /* hasSizeCompat= */ true, CAMERA_COMPAT_CONTROL_HIDDEN), mMockTaskListener);
 
         mController.onImeVisibilityChanged(DISPLAY_ID, /* isShowing= */ true);
-        mController.onKeyguardShowingChanged(true);
+        mController.onKeyguardVisibilityChanged(true, false, false);
 
         verify(mMockCompatLayout, times(2)).updateVisibility(false);
         verify(mMockLetterboxEduLayout, times(2)).updateVisibility(false);
@@ -392,7 +399,7 @@
         verify(mMockLetterboxEduLayout).updateVisibility(false);
 
         // Verify button is shown after keyguard becomes not showing.
-        mController.onKeyguardShowingChanged(false);
+        mController.onKeyguardVisibilityChanged(false, false, false);
 
         verify(mMockCompatLayout).updateVisibility(true);
         verify(mMockLetterboxEduLayout).updateVisibility(true);
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/hidedisplaycutout/HideDisplayCutoutControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/hidedisplaycutout/HideDisplayCutoutControllerTest.java
index 68e955e..dcc504a 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/hidedisplaycutout/HideDisplayCutoutControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/hidedisplaycutout/HideDisplayCutoutControllerTest.java
@@ -29,7 +29,6 @@
 import androidx.test.platform.app.InstrumentationRegistry;
 
 import com.android.wm.shell.ShellTestCase;
-import com.android.wm.shell.common.ShellExecutor;
 import com.android.wm.shell.sysui.ShellController;
 
 import org.junit.Before;
@@ -49,8 +48,6 @@
     private ShellController mShellController;
     @Mock
     private HideDisplayCutoutOrganizer mMockDisplayAreaOrganizer;
-    @Mock
-    private ShellExecutor mMockMainExecutor;
 
     private HideDisplayCutoutController mHideDisplayCutoutController;
 
@@ -58,7 +55,7 @@
     public void setUp() throws Exception {
         MockitoAnnotations.initMocks(this);
         mHideDisplayCutoutController = new HideDisplayCutoutController(
-                mContext, mShellController, mMockDisplayAreaOrganizer, mMockMainExecutor);
+                mContext, mShellController, mMockDisplayAreaOrganizer);
     }
 
     @Test
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedControllerTest.java
index 958969d..dbf93ae 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedControllerTest.java
@@ -149,6 +149,11 @@
     }
 
     @Test
+    public void testControllerRegistersKeyguardChangeListener() {
+        verify(mMockShellController, times(1)).addKeyguardChangeListener(any());
+    }
+
+    @Test
     public void testDefaultShouldNotInOneHanded() {
         // Assert default transition state is STATE_NONE
         assertThat(mSpiedTransitionState.getState()).isEqualTo(STATE_NONE);
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java
index 827785b..f192514 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java
@@ -123,6 +123,11 @@
     }
 
     @Test
+    public void instantiatePipController_registerKeyguardChangeListener() {
+        verify(mMockShellController, times(1)).addKeyguardChangeListener(any());
+    }
+
+    @Test
     public void instantiatePipController_registersPipTransitionCallback() {
         verify(mMockPipTransitionController).registerPipTransitionCallback(any());
     }
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java
index c10e4a1..c7a261f 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java
@@ -28,6 +28,9 @@
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
 
 import android.app.ActivityManager;
 import android.content.ComponentName;
@@ -44,10 +47,12 @@
 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.DisplayLayout;
 import com.android.wm.shell.common.ShellExecutor;
 import com.android.wm.shell.common.SyncTransactionQueue;
 import com.android.wm.shell.common.TransactionPool;
 import com.android.wm.shell.recents.RecentTasksController;
+import com.android.wm.shell.sysui.ShellController;
 import com.android.wm.shell.transition.Transitions;
 
 import org.junit.Before;
@@ -65,6 +70,7 @@
 @RunWith(AndroidJUnit4.class)
 public class SplitScreenControllerTests extends ShellTestCase {
 
+    @Mock ShellController mShellController;
     @Mock ShellTaskOrganizer mTaskOrganizer;
     @Mock SyncTransactionQueue mSyncQueue;
     @Mock RootTaskDisplayAreaOrganizer mRootTDAOrganizer;
@@ -82,10 +88,17 @@
     @Before
     public void setup() {
         MockitoAnnotations.initMocks(this);
-        mSplitScreenController = spy(new SplitScreenController(mTaskOrganizer, mSyncQueue, mContext,
-                mRootTDAOrganizer, mMainExecutor, mDisplayController, mDisplayImeController,
-                mDisplayInsetsController, mTransitions, mTransactionPool, mIconProvider,
-                mRecentTasks));
+        mSplitScreenController = spy(new SplitScreenController(mShellController, mTaskOrganizer,
+                mSyncQueue, mContext, mRootTDAOrganizer, mMainExecutor, mDisplayController,
+                mDisplayImeController, mDisplayInsetsController, mTransitions, mTransactionPool,
+                mIconProvider, mRecentTasks));
+    }
+
+    @Test
+    public void testControllerRegistersKeyguardChangeListener() {
+        when(mDisplayController.getDisplayLayout(anyInt())).thenReturn(new DisplayLayout());
+        mSplitScreenController.onOrganizerRegistered();
+        verify(mShellController, times(1)).addKeyguardChangeListener(any());
     }
 
     @Test
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/sysui/ShellControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/sysui/ShellControllerTest.java
index 6cc3ae8..1c0e46f 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/sysui/ShellControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/sysui/ShellControllerTest.java
@@ -46,12 +46,14 @@
     private ShellExecutor mExecutor;
 
     private ShellController mController;
-    private TestConfigurationChangeListener mListener;
+    private TestConfigurationChangeListener mConfigChangeListener;
+    private TestKeyguardChangeListener mKeyguardChangeListener;
 
     @Before
     public void setUp() {
         MockitoAnnotations.initMocks(this);
-        mListener = new TestConfigurationChangeListener();
+        mKeyguardChangeListener = new TestKeyguardChangeListener();
+        mConfigChangeListener = new TestConfigurationChangeListener();
         mController = new ShellController(mExecutor);
         mController.onConfigurationChanged(getConfigurationCopy());
     }
@@ -62,47 +64,97 @@
     }
 
     @Test
+    public void testAddKeyguardChangeListener_ensureCallback() {
+        mController.addKeyguardChangeListener(mKeyguardChangeListener);
+
+        mController.onKeyguardVisibilityChanged(true, false, false);
+        assertTrue(mKeyguardChangeListener.visibilityChanged == 1);
+        assertTrue(mKeyguardChangeListener.dismissAnimationFinished == 0);
+    }
+
+    @Test
+    public void testDoubleAddKeyguardChangeListener_ensureSingleCallback() {
+        mController.addKeyguardChangeListener(mKeyguardChangeListener);
+        mController.addKeyguardChangeListener(mKeyguardChangeListener);
+
+        mController.onKeyguardVisibilityChanged(true, false, false);
+        assertTrue(mKeyguardChangeListener.visibilityChanged == 1);
+        assertTrue(mKeyguardChangeListener.dismissAnimationFinished == 0);
+    }
+
+    @Test
+    public void testAddRemoveKeyguardChangeListener_ensureNoCallback() {
+        mController.addKeyguardChangeListener(mKeyguardChangeListener);
+        mController.removeKeyguardChangeListener(mKeyguardChangeListener);
+
+        mController.onKeyguardVisibilityChanged(true, false, false);
+        assertTrue(mKeyguardChangeListener.visibilityChanged == 0);
+        assertTrue(mKeyguardChangeListener.dismissAnimationFinished == 0);
+    }
+
+    @Test
+    public void testKeyguardVisibilityChanged() {
+        mController.addKeyguardChangeListener(mKeyguardChangeListener);
+
+        mController.onKeyguardVisibilityChanged(true, true, true);
+        assertTrue(mKeyguardChangeListener.visibilityChanged == 1);
+        assertTrue(mKeyguardChangeListener.lastAnimatingDismiss);
+        assertTrue(mKeyguardChangeListener.lastOccluded);
+        assertTrue(mKeyguardChangeListener.lastAnimatingDismiss);
+        assertTrue(mKeyguardChangeListener.dismissAnimationFinished == 0);
+    }
+
+    @Test
+    public void testKeyguardDismissAnimationFinished() {
+        mController.addKeyguardChangeListener(mKeyguardChangeListener);
+
+        mController.onKeyguardDismissAnimationFinished();
+        assertTrue(mKeyguardChangeListener.visibilityChanged == 0);
+        assertTrue(mKeyguardChangeListener.dismissAnimationFinished == 1);
+    }
+
+    @Test
     public void testAddConfigurationChangeListener_ensureCallback() {
-        mController.addConfigurationChangeListener(mListener);
+        mController.addConfigurationChangeListener(mConfigChangeListener);
 
         Configuration newConfig = getConfigurationCopy();
         newConfig.densityDpi = 200;
         mController.onConfigurationChanged(newConfig);
-        assertTrue(mListener.configChanges == 1);
+        assertTrue(mConfigChangeListener.configChanges == 1);
     }
 
     @Test
     public void testDoubleAddConfigurationChangeListener_ensureSingleCallback() {
-        mController.addConfigurationChangeListener(mListener);
-        mController.addConfigurationChangeListener(mListener);
+        mController.addConfigurationChangeListener(mConfigChangeListener);
+        mController.addConfigurationChangeListener(mConfigChangeListener);
 
         Configuration newConfig = getConfigurationCopy();
         newConfig.densityDpi = 200;
         mController.onConfigurationChanged(newConfig);
-        assertTrue(mListener.configChanges == 1);
+        assertTrue(mConfigChangeListener.configChanges == 1);
     }
 
     @Test
     public void testAddRemoveConfigurationChangeListener_ensureNoCallback() {
-        mController.addConfigurationChangeListener(mListener);
-        mController.removeConfigurationChangeListener(mListener);
+        mController.addConfigurationChangeListener(mConfigChangeListener);
+        mController.removeConfigurationChangeListener(mConfigChangeListener);
 
         Configuration newConfig = getConfigurationCopy();
         newConfig.densityDpi = 200;
         mController.onConfigurationChanged(newConfig);
-        assertTrue(mListener.configChanges == 0);
+        assertTrue(mConfigChangeListener.configChanges == 0);
     }
 
     @Test
     public void testMultipleConfigurationChangeListeners() {
         TestConfigurationChangeListener listener2 = new TestConfigurationChangeListener();
-        mController.addConfigurationChangeListener(mListener);
+        mController.addConfigurationChangeListener(mConfigChangeListener);
         mController.addConfigurationChangeListener(listener2);
 
         Configuration newConfig = getConfigurationCopy();
         newConfig.densityDpi = 200;
         mController.onConfigurationChanged(newConfig);
-        assertTrue(mListener.configChanges == 1);
+        assertTrue(mConfigChangeListener.configChanges == 1);
         assertTrue(listener2.configChanges == 1);
     }
 
@@ -115,7 +167,7 @@
             }
         };
         mController.addConfigurationChangeListener(badListener);
-        mController.addConfigurationChangeListener(mListener);
+        mController.addConfigurationChangeListener(mConfigChangeListener);
 
         // Ensure we don't fail just because a listener was removed mid-callback
         Configuration newConfig = getConfigurationCopy();
@@ -125,77 +177,77 @@
 
     @Test
     public void testDensityChangeCallback() {
-        mController.addConfigurationChangeListener(mListener);
+        mController.addConfigurationChangeListener(mConfigChangeListener);
 
         Configuration newConfig = getConfigurationCopy();
         newConfig.densityDpi = 200;
         mController.onConfigurationChanged(newConfig);
-        assertTrue(mListener.configChanges == 1);
-        assertTrue(mListener.densityChanges == 1);
-        assertTrue(mListener.smallestWidthChanges == 0);
-        assertTrue(mListener.themeChanges == 0);
-        assertTrue(mListener.localeChanges == 0);
+        assertTrue(mConfigChangeListener.configChanges == 1);
+        assertTrue(mConfigChangeListener.densityChanges == 1);
+        assertTrue(mConfigChangeListener.smallestWidthChanges == 0);
+        assertTrue(mConfigChangeListener.themeChanges == 0);
+        assertTrue(mConfigChangeListener.localeChanges == 0);
     }
 
     @Test
     public void testFontScaleChangeCallback() {
-        mController.addConfigurationChangeListener(mListener);
+        mController.addConfigurationChangeListener(mConfigChangeListener);
 
         Configuration newConfig = getConfigurationCopy();
         newConfig.fontScale = 2;
         mController.onConfigurationChanged(newConfig);
-        assertTrue(mListener.configChanges == 1);
-        assertTrue(mListener.densityChanges == 1);
-        assertTrue(mListener.smallestWidthChanges == 0);
-        assertTrue(mListener.themeChanges == 0);
-        assertTrue(mListener.localeChanges == 0);
+        assertTrue(mConfigChangeListener.configChanges == 1);
+        assertTrue(mConfigChangeListener.densityChanges == 1);
+        assertTrue(mConfigChangeListener.smallestWidthChanges == 0);
+        assertTrue(mConfigChangeListener.themeChanges == 0);
+        assertTrue(mConfigChangeListener.localeChanges == 0);
     }
 
     @Test
     public void testSmallestWidthChangeCallback() {
-        mController.addConfigurationChangeListener(mListener);
+        mController.addConfigurationChangeListener(mConfigChangeListener);
 
         Configuration newConfig = getConfigurationCopy();
         newConfig.smallestScreenWidthDp = 100;
         mController.onConfigurationChanged(newConfig);
-        assertTrue(mListener.configChanges == 1);
-        assertTrue(mListener.densityChanges == 0);
-        assertTrue(mListener.smallestWidthChanges == 1);
-        assertTrue(mListener.themeChanges == 0);
-        assertTrue(mListener.localeChanges == 0);
+        assertTrue(mConfigChangeListener.configChanges == 1);
+        assertTrue(mConfigChangeListener.densityChanges == 0);
+        assertTrue(mConfigChangeListener.smallestWidthChanges == 1);
+        assertTrue(mConfigChangeListener.themeChanges == 0);
+        assertTrue(mConfigChangeListener.localeChanges == 0);
     }
 
     @Test
     public void testThemeChangeCallback() {
-        mController.addConfigurationChangeListener(mListener);
+        mController.addConfigurationChangeListener(mConfigChangeListener);
 
         Configuration newConfig = getConfigurationCopy();
         newConfig.assetsSeq++;
         mController.onConfigurationChanged(newConfig);
-        assertTrue(mListener.configChanges == 1);
-        assertTrue(mListener.densityChanges == 0);
-        assertTrue(mListener.smallestWidthChanges == 0);
-        assertTrue(mListener.themeChanges == 1);
-        assertTrue(mListener.localeChanges == 0);
+        assertTrue(mConfigChangeListener.configChanges == 1);
+        assertTrue(mConfigChangeListener.densityChanges == 0);
+        assertTrue(mConfigChangeListener.smallestWidthChanges == 0);
+        assertTrue(mConfigChangeListener.themeChanges == 1);
+        assertTrue(mConfigChangeListener.localeChanges == 0);
     }
 
     @Test
     public void testNightModeChangeCallback() {
-        mController.addConfigurationChangeListener(mListener);
+        mController.addConfigurationChangeListener(mConfigChangeListener);
 
         Configuration newConfig = getConfigurationCopy();
         newConfig.uiMode = Configuration.UI_MODE_NIGHT_YES;
         mController.onConfigurationChanged(newConfig);
-        assertTrue(mListener.configChanges == 1);
-        assertTrue(mListener.densityChanges == 0);
-        assertTrue(mListener.smallestWidthChanges == 0);
-        assertTrue(mListener.themeChanges == 1);
-        assertTrue(mListener.localeChanges == 0);
+        assertTrue(mConfigChangeListener.configChanges == 1);
+        assertTrue(mConfigChangeListener.densityChanges == 0);
+        assertTrue(mConfigChangeListener.smallestWidthChanges == 0);
+        assertTrue(mConfigChangeListener.themeChanges == 1);
+        assertTrue(mConfigChangeListener.localeChanges == 0);
     }
 
     @Test
     public void testLocaleChangeCallback() {
-        mController.addConfigurationChangeListener(mListener);
+        mController.addConfigurationChangeListener(mConfigChangeListener);
 
         Configuration newConfig = getConfigurationCopy();
         // Just change the locales to be different
@@ -205,11 +257,11 @@
             newConfig.locale = Locale.CANADA;
         }
         mController.onConfigurationChanged(newConfig);
-        assertTrue(mListener.configChanges == 1);
-        assertTrue(mListener.densityChanges == 0);
-        assertTrue(mListener.smallestWidthChanges == 0);
-        assertTrue(mListener.themeChanges == 0);
-        assertTrue(mListener.localeChanges == 1);
+        assertTrue(mConfigChangeListener.configChanges == 1);
+        assertTrue(mConfigChangeListener.densityChanges == 0);
+        assertTrue(mConfigChangeListener.smallestWidthChanges == 0);
+        assertTrue(mConfigChangeListener.themeChanges == 0);
+        assertTrue(mConfigChangeListener.localeChanges == 1);
     }
 
     private Configuration getConfigurationCopy() {
@@ -253,4 +305,27 @@
             localeChanges++;
         }
     }
+
+    private class TestKeyguardChangeListener implements KeyguardChangeListener {
+        // Counts of number of times each of the callbacks are called
+        public int visibilityChanged;
+        public boolean lastVisibility;
+        public boolean lastOccluded;
+        public boolean lastAnimatingDismiss;
+        public int dismissAnimationFinished;
+
+        @Override
+        public void onKeyguardVisibilityChanged(boolean visible, boolean occluded,
+                boolean animatingDismiss) {
+            lastVisibility = visible;
+            lastOccluded = occluded;
+            lastAnimatingDismiss = animatingDismiss;
+            visibilityChanged++;
+        }
+
+        @Override
+        public void onKeyguardDismissAnimationFinished() {
+            dismissAnimationFinished++;
+        }
+    }
 }
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/tasksurfacehelper/TaskSurfaceHelperControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/tasksurfacehelper/TaskSurfaceHelperControllerTest.java
deleted file mode 100644
index 7583418..0000000
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/tasksurfacehelper/TaskSurfaceHelperControllerTest.java
+++ /dev/null
@@ -1,57 +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.wm.shell.tasksurfacehelper;
-
-import static org.mockito.Mockito.verify;
-
-import android.testing.AndroidTestingRunner;
-import android.view.SurfaceControl;
-
-import androidx.test.filters.SmallTest;
-
-import com.android.wm.shell.ShellTaskOrganizer;
-import com.android.wm.shell.ShellTestCase;
-import com.android.wm.shell.common.ShellExecutor;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-
-@RunWith(AndroidTestingRunner.class)
-@SmallTest
-public class TaskSurfaceHelperControllerTest extends ShellTestCase {
-    private TaskSurfaceHelperController mTaskSurfaceHelperController;
-    @Mock
-    private ShellTaskOrganizer mMockTaskOrganizer;
-    @Mock
-    private ShellExecutor mMockShellExecutor;
-
-    @Before
-    public void setUp() throws Exception {
-        MockitoAnnotations.initMocks(this);
-        mTaskSurfaceHelperController = new TaskSurfaceHelperController(
-                mMockTaskOrganizer, mMockShellExecutor);
-    }
-
-    @Test
-    public void testSetGameModeForTask() {
-        mTaskSurfaceHelperController.setGameModeForTask(/*taskId*/1, /*gameMode*/3);
-        verify(mMockTaskOrganizer).setSurfaceMetadata(1, SurfaceControl.METADATA_GAME_MODE, 3);
-    }
-}
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/SettingsLib/res/values/strings.xml b/packages/SettingsLib/res/values/strings.xml
index f92cc8e..4f84c8c 100644
--- a/packages/SettingsLib/res/values/strings.xml
+++ b/packages/SettingsLib/res/values/strings.xml
@@ -246,9 +246,9 @@
     <!-- Bluetooth settings. The user-visible string that is used whenever referring to the PAN profile (accessing Internet through remote device). [CHAR LIMIT=40] -->
     <string name="bluetooth_profile_pan">Internet access</string>
     <!-- Bluetooth settings. The user-visible string that is used whenever referring to the PBAP profile. [CHAR LIMIT=40] -->
-    <string name="bluetooth_profile_pbap">Contact sharing</string>
+    <string name="bluetooth_profile_pbap">Contacts and call history sharing</string>
     <!-- Bluetooth settings. The user-visible summary string that is used whenever referring to the PBAP profile (sharing contacts). [CHAR LIMIT=60] -->
-    <string name="bluetooth_profile_pbap_summary">Use for contact sharing</string>
+    <string name="bluetooth_profile_pbap_summary">Use for contacts and call history sharing</string>
     <!-- Bluetooth settings. The user-visible string that is used whenever referring to the PAN profile (sharing this device's Internet connection). [CHAR LIMIT=40] -->
     <string name="bluetooth_profile_pan_nap">Internet connection sharing</string>
     <!-- Bluetooth settings. The user-visible string that is used whenever referring to the map profile. -->
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityLaunchAnimator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityLaunchAnimator.kt
index fbe3356..8ddd430 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityLaunchAnimator.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityLaunchAnimator.kt
@@ -523,9 +523,10 @@
             state: LaunchAnimator.State,
             linearProgress: Float,
         ) {
-            if (transactionApplierView.viewRootImpl == null) {
-                // If the view root we synchronize with was detached, don't apply any transaction
-                // (as [SyncRtSurfaceTransactionApplier.scheduleApply] would otherwise throw).
+            if (transactionApplierView.viewRootImpl == null || !window.leash.isValid) {
+                // Don't apply any transaction if the view root we synchronize with was detached or
+                // if the SurfaceControl associated with [window] is not valid, as
+                // [SyncRtSurfaceTransactionApplier.scheduleApply] would otherwise throw.
                 return
             }
 
@@ -605,9 +606,10 @@
             state: LaunchAnimator.State,
             linearProgress: Float
         ) {
-            if (transactionApplierView.viewRootImpl == null) {
-                // If the view root we synchronize with was detached, don't apply any transaction
-                // (as [SyncRtSurfaceTransactionApplier.scheduleApply] would otherwise throw).
+            if (transactionApplierView.viewRootImpl == null || !navigationBar.leash.isValid) {
+                // Don't apply any transaction if the view root we synchronize with was detached or
+                // if the SurfaceControl associated with [navigationBar] is not valid, as
+                // [SyncRtSurfaceTransactionApplier.scheduleApply] would otherwise throw.
                 return
             }
 
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/animation/src/com/android/systemui/animation/GhostedViewLaunchAnimatorController.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/GhostedViewLaunchAnimatorController.kt
index 47f448d..eb000ad 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/GhostedViewLaunchAnimatorController.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/GhostedViewLaunchAnimatorController.kt
@@ -49,17 +49,16 @@
  * Note: Avoid instantiating this directly and call [ActivityLaunchAnimator.Controller.fromView]
  * whenever possible instead.
  */
-open class GhostedViewLaunchAnimatorController(
+open class GhostedViewLaunchAnimatorController @JvmOverloads constructor(
     /** The view that will be ghosted and from which the background will be extracted. */
     private val ghostedView: View,
 
     /** The [InteractionJankMonitor.CujType] associated to this animation. */
     private val cujType: Int? = null,
-    private var interactionJankMonitor: InteractionJankMonitor? = null
+    private var interactionJankMonitor: InteractionJankMonitor =
+        InteractionJankMonitor.getInstance(),
 ) : ActivityLaunchAnimator.Controller {
 
-    constructor(view: View, type: Int) : this(view, type, null)
-
     /** The container to which we will add the ghost view and expanding background. */
     override var launchContainer = ghostedView.rootView as ViewGroup
     private val launchContainerOverlay: ViewGroupOverlay
@@ -203,7 +202,7 @@
         val matrix = ghostView?.animationMatrix ?: Matrix.IDENTITY_MATRIX
         matrix.getValues(initialGhostViewMatrixValues)
 
-        cujType?.let { interactionJankMonitor?.begin(ghostedView, it) }
+        cujType?.let { interactionJankMonitor.begin(ghostedView, it) }
     }
 
     override fun onLaunchAnimationProgress(
@@ -289,7 +288,7 @@
             return
         }
 
-        cujType?.let { interactionJankMonitor?.end(it) }
+        cujType?.let { interactionJankMonitor.end(it) }
 
         backgroundDrawable?.wrapped?.alpha = startBackgroundAlpha
 
diff --git a/packages/SystemUI/docs/user-file-manager.md b/packages/SystemUI/docs/user-file-manager.md
index 64f1694..52fa206 100644
--- a/packages/SystemUI/docs/user-file-manager.md
+++ b/packages/SystemUI/docs/user-file-manager.md
@@ -1,10 +1,30 @@
 # UserFileManager
 
-This class is used to generate file paths and SharedPreferences that is compatible for multiple
+This class is used to generate file paths and SharedPreferences that is compatible for specific OS
 users in SystemUI. Due to constraints in SystemUI, we can only read/write files as the system user.
 Therefore, for secondary users, we want to store secondary user specific files into the system user
 directory.
 
+
+## Usages
+
+Inject UserFileManager into your class.
+
+### fun getFile(fileName: String, userId: Int): File
+Add a file name and user id. You can retrieve the current user id from UserTracker. This will
+return a java.io File object that contains the file path to write/read to.
+
+i.e. `fileManager.getFile("example.xml", userTracker.userId)`
+
+### fun getSharedPreferences(fileName: String, mode: Int, userId: Int): SharedPreferences
+Add a file name, user id, and PreferencesMode. You can retrieve the current user id from
+UserTracker. This returns SharedPreferences object that is tied to the specific user. Note that if
+the SharedPreferences file does not exist, one will be created automatically. See
+[SharedPreferences documentation](https://developer.android.com/reference/android/content/Context#getSharedPreferences(java.lang.String,%20int))
+for more details.
+
+i.e. `fileManager.getSharedPreferences("prefs.xml", userTracker.userId, 0)`
+
 ## Handling User Removal
 
 This class will listen for Intent.ACTION_USER_REMOVED and remove directories that no longer
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/ClockPlugin.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/ClockPlugin.java
index 3058d94..bef61b8 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/ClockPlugin.java
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/ClockPlugin.java
@@ -23,7 +23,9 @@
 
 /**
  * Plugin used to replace main clock in keyguard.
+ * @deprecated Migrating to ClockProviderPlugin
  */
+@Deprecated
 @ProvidesInterface(action = ClockPlugin.ACTION, version = ClockPlugin.VERSION)
 public interface ClockPlugin extends Plugin {
 
diff --git a/packages/SystemUI/res/layout/clipboard_overlay.xml b/packages/SystemUI/res/layout/clipboard_overlay.xml
index 99a5a2e..1a1fc75 100644
--- a/packages/SystemUI/res/layout/clipboard_overlay.xml
+++ b/packages/SystemUI/res/layout/clipboard_overlay.xml
@@ -115,6 +115,7 @@
                   android:autoSizeMinTextSize="@dimen/clipboard_overlay_min_font"
                   android:autoSizeMaxTextSize="@dimen/clipboard_overlay_max_font"
                   android:textColor="?attr/overlayButtonTextColor"
+                  android:textColorLink="?attr/overlayButtonTextColor"
                   android:background="?androidprv:attr/colorAccentSecondary"
                   android:layout_width="@dimen/clipboard_preview_size"
                   android:layout_height="@dimen/clipboard_preview_size"/>
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/layout/keyguard_status_bar.xml b/packages/SystemUI/res/layout/keyguard_status_bar.xml
index e47eed9..d27fa19 100644
--- a/packages/SystemUI/res/layout/keyguard_status_bar.xml
+++ b/packages/SystemUI/res/layout/keyguard_status_bar.xml
@@ -60,9 +60,8 @@
         </com.android.systemui.statusbar.phone.userswitcher.StatusBarUserSwitcherContainer>
 
         <FrameLayout android:id="@+id/system_icons_container"
-            android:layout_width="0dp"
+            android:layout_width="wrap_content"
             android:layout_height="match_parent"
-            android:layout_weight="1"
             android:layout_marginEnd="@dimen/status_bar_padding_end"
             android:gravity="center_vertical|end">
             <include layout="@layout/system_icons" />
diff --git a/packages/SystemUI/res/layout/notification_icon_area.xml b/packages/SystemUI/res/layout/notification_icon_area.xml
index fa696cc..aadfae8 100644
--- a/packages/SystemUI/res/layout/notification_icon_area.xml
+++ b/packages/SystemUI/res/layout/notification_icon_area.xml
@@ -14,18 +14,9 @@
   ~ See the License for the specific language governing permissions and
   ~ limitations under the License
   -->
-<com.android.keyguard.AlphaOptimizedLinearLayout
+<com.android.systemui.statusbar.phone.NotificationIconContainer
     xmlns:android="http://schemas.android.com/apk/res/android"
-    android:id="@+id/notification_icon_area_inner"
-    android:layout_width="match_parent"
+    android:id="@+id/notificationIcons"
+    android:layout_width="wrap_content"
     android:layout_height="match_parent"
-    android:clipChildren="false">
-    <com.android.systemui.statusbar.phone.NotificationIconContainer
-        android:id="@+id/notificationIcons"
-        android:layout_width="match_parent"
-        android:layout_height="match_parent"
-        android:layout_alignParentStart="true"
-        android:gravity="center_vertical"
-        android:orientation="horizontal"
-        android:clipChildren="false"/>
-</com.android.keyguard.AlphaOptimizedLinearLayout>
\ No newline at end of file
+    android:clipChildren="false"/>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/status_bar.xml b/packages/SystemUI/res/layout/status_bar.xml
index deab1eb..e281511 100644
--- a/packages/SystemUI/res/layout/status_bar.xml
+++ b/packages/SystemUI/res/layout/status_bar.xml
@@ -47,52 +47,63 @@
         android:paddingStart="@dimen/status_bar_padding_start"
         android:paddingEnd="@dimen/status_bar_padding_end"
         android:paddingTop="@dimen/status_bar_padding_top"
-        android:orientation="horizontal"
-        >
+        android:orientation="horizontal">
+
+        <!-- Container for the entire start half of the status bar. It will always use the same
+             width, independent of the number of visible children and sub-children. -->
         <FrameLayout
+            android:id="@+id/status_bar_start_side_container"
             android:layout_height="match_parent"
             android:layout_width="0dp"
             android:layout_weight="1">
 
-            <include layout="@layout/heads_up_status_bar_layout" />
+            <!-- Container that is wrapped around the views on the start half of the status bar.
+                 Its width will change with the number of visible children and sub-children.
+                 It is useful when we want to know the visible bounds of the content. -->
+            <FrameLayout
+                android:id="@+id/status_bar_start_side_content"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:clipChildren="false">
 
-            <!-- The alpha of the left side is controlled by PhoneStatusBarTransitions, and the
-             individual views are controlled by StatusBarManager disable flags DISABLE_CLOCK and
-             DISABLE_NOTIFICATION_ICONS, respectively -->
-            <LinearLayout
-                android:id="@+id/status_bar_left_side"
-                android:layout_height="match_parent"
-                android:layout_width="match_parent"
-                android:clipChildren="false"
-            >
-                <ViewStub
-                    android:id="@+id/operator_name"
-                    android:layout_width="wrap_content"
-                    android:layout_height="match_parent"
-                    android:layout="@layout/operator_name" />
+                <include layout="@layout/heads_up_status_bar_layout" />
 
-                <com.android.systemui.statusbar.policy.Clock
-                    android:id="@+id/clock"
-                    android:layout_width="wrap_content"
-                    android:layout_height="match_parent"
-                    android:textAppearance="@style/TextAppearance.StatusBar.Clock"
-                    android:singleLine="true"
-                    android:paddingStart="@dimen/status_bar_left_clock_starting_padding"
-                    android:paddingEnd="@dimen/status_bar_left_clock_end_padding"
-                    android:gravity="center_vertical|start"
-                />
+                <!-- The alpha of the start side is controlled by PhoneStatusBarTransitions, and the
+                     individual views are controlled by StatusBarManager disable flags DISABLE_CLOCK
+                     and DISABLE_NOTIFICATION_ICONS, respectively -->
+                <LinearLayout
+                    android:id="@+id/status_bar_start_side_except_heads_up"
+                    android:layout_height="wrap_content"
+                    android:layout_width="match_parent"
+                    android:clipChildren="false">
+                    <ViewStub
+                        android:id="@+id/operator_name"
+                        android:layout_width="wrap_content"
+                        android:layout_height="match_parent"
+                        android:layout="@layout/operator_name" />
 
-                <include layout="@layout/ongoing_call_chip" />
+                    <com.android.systemui.statusbar.policy.Clock
+                        android:id="@+id/clock"
+                        android:layout_width="wrap_content"
+                        android:layout_height="match_parent"
+                        android:textAppearance="@style/TextAppearance.StatusBar.Clock"
+                        android:singleLine="true"
+                        android:paddingStart="@dimen/status_bar_left_clock_starting_padding"
+                        android:paddingEnd="@dimen/status_bar_left_clock_end_padding"
+                        android:gravity="center_vertical|start"
+                    />
 
-                <com.android.systemui.statusbar.AlphaOptimizedFrameLayout
-                    android:id="@+id/notification_icon_area"
-                    android:layout_width="0dp"
-                    android:layout_height="match_parent"
-                    android:layout_weight="1"
-                    android:orientation="horizontal"
-                    android:clipChildren="false"/>
+                    <include layout="@layout/ongoing_call_chip" />
 
-            </LinearLayout>
+                    <com.android.systemui.statusbar.AlphaOptimizedFrameLayout
+                        android:id="@+id/notification_icon_area"
+                        android:layout_width="wrap_content"
+                        android:layout_height="match_parent"
+                        android:orientation="horizontal"
+                        android:clipChildren="false"/>
+
+                </LinearLayout>
+            </FrameLayout>
         </FrameLayout>
 
         <!-- Space should cover the notch (if it exists) and let other views lay out around it -->
@@ -103,42 +114,57 @@
             android:gravity="center_horizontal|center_vertical"
         />
 
-        <com.android.keyguard.AlphaOptimizedLinearLayout android:id="@+id/system_icon_area"
+        <!-- Container for the entire end half of the status bar. It will always use the same
+             width, independent of the number of visible children and sub-children. -->
+        <FrameLayout
+            android:id="@+id/status_bar_end_side_container"
             android:layout_width="0dp"
             android:layout_height="match_parent"
             android:layout_weight="1"
-            android:orientation="horizontal"
-            android:gravity="center_vertical|end"
-            >
+            android:clipChildren="false">
 
-            <com.android.systemui.statusbar.phone.userswitcher.StatusBarUserSwitcherContainer
-                android:id="@+id/user_switcher_container"
+            <!-- Container that is wrapped around the views on the end half of the
+                 status bar. Its width will change with the number of visible children and
+                 sub-children.
+                 It is useful when we want know the visible bounds of the content.-->
+            <com.android.keyguard.AlphaOptimizedLinearLayout
+                android:id="@+id/status_bar_end_side_content"
                 android:layout_width="wrap_content"
-                android:layout_height="wrap_content"
-                android:gravity="center"
+                android:layout_height="match_parent"
+                android:layout_gravity="end"
                 android:orientation="horizontal"
-                android:paddingTop="4dp"
-                android:paddingBottom="4dp"
-                android:paddingStart="8dp"
-                android:paddingEnd="8dp"
-                android:layout_marginEnd="16dp"
-                android:background="@drawable/status_bar_user_chip_bg"
-                android:visibility="visible" >
-                <ImageView android:id="@+id/current_user_avatar"
-                    android:layout_width="@dimen/multi_user_avatar_keyguard_size"
-                    android:layout_height="@dimen/multi_user_avatar_keyguard_size"
-                    android:scaleType="centerInside"
-                    android:paddingEnd="4dp" />
+                android:gravity="center_vertical|end"
+                android:clipChildren="false">
 
-                <TextView android:id="@+id/current_user_name"
+                <com.android.systemui.statusbar.phone.userswitcher.StatusBarUserSwitcherContainer
+                    android:id="@+id/user_switcher_container"
                     android:layout_width="wrap_content"
                     android:layout_height="wrap_content"
-                    android:textAppearance="@style/TextAppearance.StatusBar.Clock"
-                    />
-            </com.android.systemui.statusbar.phone.userswitcher.StatusBarUserSwitcherContainer>
+                    android:gravity="center"
+                    android:orientation="horizontal"
+                    android:paddingTop="4dp"
+                    android:paddingBottom="4dp"
+                    android:paddingStart="8dp"
+                    android:paddingEnd="8dp"
+                    android:layout_marginEnd="16dp"
+                    android:background="@drawable/status_bar_user_chip_bg"
+                    android:visibility="visible" >
+                    <ImageView android:id="@+id/current_user_avatar"
+                        android:layout_width="@dimen/multi_user_avatar_keyguard_size"
+                        android:layout_height="@dimen/multi_user_avatar_keyguard_size"
+                        android:scaleType="centerInside"
+                        android:paddingEnd="4dp" />
 
-            <include layout="@layout/system_icons" />
-        </com.android.keyguard.AlphaOptimizedLinearLayout>
+                    <TextView android:id="@+id/current_user_name"
+                        android:layout_width="wrap_content"
+                        android:layout_height="wrap_content"
+                        android:textAppearance="@style/TextAppearance.StatusBar.Clock"
+                        />
+                </com.android.systemui.statusbar.phone.userswitcher.StatusBarUserSwitcherContainer>
+
+                <include layout="@layout/system_icons" />
+            </com.android.keyguard.AlphaOptimizedLinearLayout>
+        </FrameLayout>
     </LinearLayout>
 
     <ViewStub
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/Android.bp b/packages/SystemUI/shared/Android.bp
index 9f790c6..114ea65 100644
--- a/packages/SystemUI/shared/Android.bp
+++ b/packages/SystemUI/shared/Android.bp
@@ -50,6 +50,7 @@
         "SystemUIUnfoldLib",
         "androidx.dynamicanimation_dynamicanimation",
         "androidx.concurrent_concurrent-futures",
+        "gson-prebuilt-jar",
         "dagger2",
         "jsr330",
     ],
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/shared/src/com/android/systemui/shared/clocks/ClockProviderPlugin.kt b/packages/SystemUI/shared/src/com/android/systemui/shared/clocks/ClockProviderPlugin.kt
new file mode 100644
index 0000000..916a557
--- /dev/null
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/clocks/ClockProviderPlugin.kt
@@ -0,0 +1,65 @@
+/*
+ * 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.shared.clocks
+
+import com.android.systemui.plugins.Plugin
+import com.android.systemui.plugins.annotations.ProvidesInterface
+import android.annotation.FloatRange
+import android.graphics.drawable.Drawable
+import android.view.View
+
+/** Identifies a clock design */
+typealias ClockId = String
+
+/** A Plugin which exposes the ClockProvider interface */
+@ProvidesInterface(action = ClockProviderPlugin.ACTION, version = ClockProviderPlugin.VERSION)
+interface ClockProviderPlugin : Plugin, ClockProvider {
+    companion object {
+        const val ACTION = "com.android.systemui.action.PLUGIN_CLOCK_PROVIDER"
+        const val VERSION = 1
+    }
+}
+
+/** Interface for building clocks and providing information about those clocks */
+interface ClockProvider {
+    /** Returns metadata for all clocks this provider knows about */
+    fun getClocks(): List<ClockMetadata>
+
+    /** Initializes and returns the target clock design */
+    fun createClock(id: ClockId): Clock
+
+    /** A static thumbnail for rendering in some examples */
+    fun getClockThumbnail(id: ClockId): Drawable?
+}
+
+/** Interface for controlling an active clock */
+interface Clock {
+    /** A small version of the clock, appropriate for smaller viewports */
+    val smallClock: View
+
+    /** A large version of the clock, appropriate when a bigger viewport is available */
+    val largeClock: View
+
+    /** Callback to update the clock view to the current time */
+    fun onTimeTick()
+
+    /** Sets the level of the AOD transition */
+    fun setAodFraction(@FloatRange(from = 0.0, to = 1.0) fraction: Float)
+}
+
+/** Some data about a clock design */
+data class ClockMetadata(
+    val clockId: ClockId,
+    val name: String
+)
\ No newline at end of file
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/clocks/ClockRegistry.kt b/packages/SystemUI/shared/src/com/android/systemui/shared/clocks/ClockRegistry.kt
new file mode 100644
index 0000000..3245966
--- /dev/null
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/clocks/ClockRegistry.kt
@@ -0,0 +1,146 @@
+/*
+ * 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.shared.clocks
+
+import android.content.Context
+import android.database.ContentObserver
+import android.graphics.drawable.Drawable
+import android.net.Uri
+import android.os.Handler
+import android.os.UserHandle
+import android.provider.Settings
+import android.util.Log
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.plugins.PluginListener
+import com.android.systemui.shared.plugins.PluginManager
+import com.google.gson.Gson
+import javax.inject.Inject
+
+private val TAG = ClockRegistry::class.simpleName
+private val DEBUG = true
+const val DEFAULT_CLOCK_ID = "DEFAULT"
+
+typealias ClockChangeListener = () -> Unit
+
+/** ClockRegistry aggregates providers and plugins */
+open class ClockRegistry @Inject constructor(
+    val context: Context,
+    val pluginManager: PluginManager,
+    @Main val handler: Handler
+) {
+    private val gson = Gson()
+    private val availableClocks = mutableMapOf<ClockId, ClockInfo>()
+    private val clockChangeListeners = mutableListOf<ClockChangeListener>()
+    private val settingObserver = object : ContentObserver(handler) {
+        override fun onChange(selfChange: Boolean, uris: Collection<Uri>, flags: Int, userId: Int) =
+            clockChangeListeners.forEach { it() }
+    }
+
+    private val pluginListener = object : PluginListener<ClockProviderPlugin> {
+        override fun onPluginConnected(plugin: ClockProviderPlugin, context: Context) {
+            val currentId = currentClockId
+            for (clock in plugin.getClocks()) {
+                val id = clock.clockId
+                val current = availableClocks[id]
+                if (current != null) {
+                    Log.e(TAG, "Clock Id conflict: $id is registered by both " +
+                            "${plugin::class.simpleName} and ${current.provider::class.simpleName}")
+                    return
+                }
+
+                availableClocks[id] = ClockInfo(clock, plugin)
+
+                if (currentId == id) {
+                    if (DEBUG) {
+                        Log.i(TAG, "Current clock ($currentId) was connected")
+                    }
+                    clockChangeListeners.forEach { it() }
+                }
+            }
+        }
+
+        override fun onPluginDisconnected(plugin: ClockProviderPlugin) {
+            val currentId = currentClockId
+            for (clock in plugin.getClocks()) {
+                availableClocks.remove(clock.clockId)
+
+                if (currentId == clock.clockId) {
+                    Log.w(TAG, "Current clock ($currentId) was disconnected")
+                    clockChangeListeners.forEach { it() }
+                }
+            }
+        }
+    }
+
+    open var currentClockId: ClockId
+        get() {
+            val json = Settings.Secure.getString(context.contentResolver,
+                Settings.Secure.LOCK_SCREEN_CUSTOM_CLOCK_FACE)
+            return gson.fromJson(json, ClockSetting::class.java).clockId
+        }
+        set(value) {
+            val json = gson.toJson(ClockSetting(value, System.currentTimeMillis()))
+            Settings.Secure.putString(context.contentResolver,
+                Settings.Secure.LOCK_SCREEN_CUSTOM_CLOCK_FACE, json)
+        }
+
+    init {
+        pluginManager.addPluginListener(pluginListener, ClockProviderPlugin::class.java)
+        context.contentResolver.registerContentObserver(
+            Settings.Secure.getUriFor(Settings.Secure.LOCK_SCREEN_CUSTOM_CLOCK_FACE),
+            false,
+            settingObserver,
+            UserHandle.USER_ALL)
+    }
+
+    fun getClocks(): List<ClockMetadata> = availableClocks.map { (_, clock) -> clock.metadata }
+
+    fun getClockThumbnail(clockId: ClockId): Drawable? =
+        availableClocks[clockId]?.provider?.getClockThumbnail(clockId)
+
+    fun createExampleClock(clockId: ClockId): Clock? = createClock(clockId)
+
+    fun registerClockChangeListener(listener: ClockChangeListener) =
+        clockChangeListeners.add(listener)
+
+    fun unregisterClockChangeListener(listener: ClockChangeListener) =
+        clockChangeListeners.remove(listener)
+
+    fun createCurrentClock(): Clock {
+        val clockId = currentClockId
+        if (!clockId.isNullOrEmpty()) {
+            val clock = createClock(clockId)
+            if (clock != null) {
+                return clock
+            } else {
+                Log.e(TAG, "Clock $clockId not found; using default")
+            }
+        }
+
+        return createClock(DEFAULT_CLOCK_ID)!!
+    }
+
+    private fun createClock(clockId: ClockId): Clock? =
+        availableClocks[clockId]?.provider?.createClock(clockId)
+
+    private data class ClockInfo(
+        val metadata: ClockMetadata,
+        val provider: ClockProvider
+    )
+
+    private data class ClockSetting(
+        val clockId: ClockId,
+        val _applied_timestamp: Long
+    )
+}
\ No newline at end of file
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RecentsAnimationControllerCompat.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RecentsAnimationControllerCompat.java
index 13f1db4a..0094820 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RecentsAnimationControllerCompat.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RecentsAnimationControllerCompat.java
@@ -65,14 +65,6 @@
         }
     }
 
-    public void hideCurrentInputMethod() {
-        try {
-            mAnimationController.hideCurrentInputMethod();
-        } catch (RemoteException e) {
-            Log.e(TAG, "Failed to set hide input method", e);
-        }
-    }
-
     /**
      * Sets the final surface transaction on a Task. This is used by Launcher to notify the system
      * that animating Activity to PiP has completed and the associated task surface should be
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.java
index ff2a7a1..609846e 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.java
@@ -369,10 +369,6 @@
             if (mWrapped != null) mWrapped.setAnimationTargetsBehindSystemBars(behindSystemBars);
         }
 
-        @Override public void hideCurrentInputMethod() {
-            mWrapped.hideCurrentInputMethod();
-        }
-
         @Override public void setFinishTaskTransaction(int taskId,
                 PictureInPictureSurfaceTransaction finishTransaction, SurfaceControl overlay) {
             mPipTransaction = finishTransaction;
diff --git a/packages/SystemUI/src/com/android/keyguard/clock/ClockManager.java b/packages/SystemUI/src/com/android/keyguard/clock/ClockManager.java
index 013cdac..9a0bfc1 100644
--- a/packages/SystemUI/src/com/android/keyguard/clock/ClockManager.java
+++ b/packages/SystemUI/src/com/android/keyguard/clock/ClockManager.java
@@ -53,8 +53,11 @@
 
 /**
  * Manages custom clock faces for AOD and lock screen.
+ *
+ * @deprecated Migrate to ClockRegistry
  */
 @SysUISingleton
+@Deprecated
 public final class ClockManager {
 
     private static final String TAG = "ClockOptsProvider";
diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIInitializer.java b/packages/SystemUI/src/com/android/systemui/SystemUIInitializer.java
index 08096b0..e9ca0fd 100644
--- a/packages/SystemUI/src/com/android/systemui/SystemUIInitializer.java
+++ b/packages/SystemUI/src/com/android/systemui/SystemUIInitializer.java
@@ -96,16 +96,11 @@
                     .setSplitScreen(mWMComponent.getSplitScreen())
                     .setOneHanded(mWMComponent.getOneHanded())
                     .setBubbles(mWMComponent.getBubbles())
-                    .setHideDisplayCutout(mWMComponent.getHideDisplayCutout())
-                    .setShellCommandHandler(mWMComponent.getShellCommandHandler())
                     .setTaskViewFactory(mWMComponent.getTaskViewFactory())
                     .setTransitions(mWMComponent.getTransitions())
                     .setStartingSurface(mWMComponent.getStartingSurface())
                     .setDisplayAreaHelper(mWMComponent.getDisplayAreaHelper())
-                    .setTaskSurfaceHelper(mWMComponent.getTaskSurfaceHelper())
                     .setRecentTasks(mWMComponent.getRecentTasks())
-                    .setCompatUI(mWMComponent.getCompatUI())
-                    .setDragAndDrop(mWMComponent.getDragAndDrop())
                     .setBackAnimation(mWMComponent.getBackAnimation());
         } else {
             // TODO: Call on prepareSysUIComponentBuilder but not with real components. Other option
@@ -116,16 +111,11 @@
                     .setSplitScreen(Optional.ofNullable(null))
                     .setOneHanded(Optional.ofNullable(null))
                     .setBubbles(Optional.ofNullable(null))
-                    .setHideDisplayCutout(Optional.ofNullable(null))
-                    .setShellCommandHandler(Optional.ofNullable(null))
                     .setTaskViewFactory(Optional.ofNullable(null))
                     .setTransitions(new ShellTransitions() {})
                     .setDisplayAreaHelper(Optional.ofNullable(null))
                     .setStartingSurface(Optional.ofNullable(null))
-                    .setTaskSurfaceHelper(Optional.ofNullable(null))
                     .setRecentTasks(Optional.ofNullable(null))
-                    .setCompatUI(Optional.ofNullable(null))
-                    .setDragAndDrop(Optional.ofNullable(null))
                     .setBackAnimation(Optional.ofNullable(null));
         }
         mSysUIComponent = builder.build();
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
index fb502e5..cf50f7f 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
@@ -609,7 +609,7 @@
             @NonNull SystemUIDialogManager dialogManager,
             @NonNull LatencyTracker latencyTracker,
             @NonNull ActivityLaunchAnimator activityLaunchAnimator,
-            @NonNull Optional<AlternateUdfpsTouchProvider> aternateTouchProvider,
+            @NonNull Optional<AlternateUdfpsTouchProvider> alternateTouchProvider,
             @BiometricsBackground Executor biometricsExecutor) {
         mContext = context;
         mExecution = execution;
@@ -639,7 +639,7 @@
         mUnlockedScreenOffAnimationController = unlockedScreenOffAnimationController;
         mLatencyTracker = latencyTracker;
         mActivityLaunchAnimator = activityLaunchAnimator;
-        mAlternateTouchProvider = aternateTouchProvider.orElse(null);
+        mAlternateTouchProvider = alternateTouchProvider.orElse(null);
         mBiometricExecutor = biometricsExecutor;
 
         mOrientationListener = new BiometricDisplayListener(
diff --git a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsControllerImpl.kt
index bed553e..50ce9d4 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsControllerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsControllerImpl.kt
@@ -20,13 +20,11 @@
 import android.app.backup.BackupManager
 import android.content.BroadcastReceiver
 import android.content.ComponentName
-import android.content.ContentResolver
 import android.content.Context
 import android.content.Intent
 import android.content.IntentFilter
 import android.database.ContentObserver
 import android.net.Uri
-import android.os.Environment
 import android.os.UserHandle
 import android.service.controls.Control
 import android.service.controls.actions.ControlAction
@@ -43,6 +41,7 @@
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Background
 import com.android.systemui.dump.DumpManager
+import com.android.systemui.settings.UserFileManager
 import com.android.systemui.settings.UserTracker
 import com.android.systemui.statusbar.policy.DeviceControlsControllerImpl.Companion.PREFS_CONTROLS_FILE
 import com.android.systemui.statusbar.policy.DeviceControlsControllerImpl.Companion.PREFS_CONTROLS_SEEDING_COMPLETED
@@ -61,6 +60,7 @@
     private val bindingController: ControlsBindingController,
     private val listingController: ControlsListingController,
     private val broadcastDispatcher: BroadcastDispatcher,
+    private val userFileManager: UserFileManager,
     optionalWrapper: Optional<ControlsFavoritePersistenceWrapper>,
     dumpManager: DumpManager,
     userTracker: UserTracker
@@ -84,15 +84,12 @@
     override val currentUserId
         get() = currentUser.identifier
 
-    private val contentResolver: ContentResolver
-        get() = context.contentResolver
-
     private val persistenceWrapper: ControlsFavoritePersistenceWrapper
     @VisibleForTesting
     internal var auxiliaryPersistenceWrapper: AuxiliaryPersistenceWrapper
 
     init {
-        userStructure = UserStructure(context, currentUser)
+        userStructure = UserStructure(context, currentUser, userFileManager)
 
         persistenceWrapper = optionalWrapper.orElseGet {
             ControlsFavoritePersistenceWrapper(
@@ -111,7 +108,7 @@
     private fun setValuesForUser(newUser: UserHandle) {
         Log.d(TAG, "Changing to user: $newUser")
         currentUser = newUser
-        userStructure = UserStructure(context, currentUser)
+        userStructure = UserStructure(context, currentUser, userFileManager)
         persistenceWrapper.changeFileAndBackupManager(
                 userStructure.file,
                 BackupManager(userStructure.userContext)
@@ -187,8 +184,11 @@
 
                 // When a component is uninstalled, allow seeding to happen again if the user
                 // reinstalls the app
-                val prefs = userStructure.userContext.getSharedPreferences(
-                    PREFS_CONTROLS_FILE, Context.MODE_PRIVATE)
+                val prefs = userFileManager.getSharedPreferences(
+                    PREFS_CONTROLS_FILE,
+                    Context.MODE_PRIVATE,
+                    userTracker.userId
+                )
                 val completedSeedingPackageSet = prefs.getStringSet(
                     PREFS_CONTROLS_SEEDING_COMPLETED, mutableSetOf<String>())
                 val servicePackageSet = serviceInfoSet.map { it.packageName }
@@ -575,18 +575,12 @@
     }
 }
 
-class UserStructure(context: Context, user: UserHandle) {
+class UserStructure(context: Context, user: UserHandle, userFileManager: UserFileManager) {
     val userContext = context.createContextAsUser(user, 0)
-
-    val file = Environment.buildPath(
-            userContext.filesDir,
-            ControlsFavoritePersistenceWrapper.FILE_NAME
-    )
-
-    val auxiliaryFile = Environment.buildPath(
-            userContext.filesDir,
-            AuxiliaryPersistenceWrapper.AUXILIARY_FILE_NAME
-    )
+    val file = userFileManager.getFile(ControlsFavoritePersistenceWrapper.FILE_NAME,
+        user.identifier)
+    val auxiliaryFile = userFileManager.getFile(AuxiliaryPersistenceWrapper.AUXILIARY_FILE_NAME,
+        user.identifier)
 }
 
 /**
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java b/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java
index adeafd5..029cabb 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java
@@ -37,21 +37,16 @@
 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;
-import com.android.wm.shell.compatui.CompatUI;
 import com.android.wm.shell.displayareahelper.DisplayAreaHelper;
-import com.android.wm.shell.draganddrop.DragAndDrop;
-import com.android.wm.shell.hidedisplaycutout.HideDisplayCutout;
 import com.android.wm.shell.onehanded.OneHanded;
 import com.android.wm.shell.pip.Pip;
 import com.android.wm.shell.recents.RecentTasks;
 import com.android.wm.shell.splitscreen.SplitScreen;
 import com.android.wm.shell.startingsurface.StartingSurface;
 import com.android.wm.shell.sysui.ShellInterface;
-import com.android.wm.shell.tasksurfacehelper.TaskSurfaceHelper;
 import com.android.wm.shell.transition.ShellTransitions;
 
 import java.util.Map;
@@ -100,12 +95,6 @@
         Builder setTaskViewFactory(Optional<TaskViewFactory> t);
 
         @BindsInstance
-        Builder setHideDisplayCutout(Optional<HideDisplayCutout> h);
-
-        @BindsInstance
-        Builder setShellCommandHandler(Optional<ShellCommandHandler> shellDump);
-
-        @BindsInstance
         Builder setTransitions(ShellTransitions t);
 
         @BindsInstance
@@ -115,18 +104,9 @@
         Builder setDisplayAreaHelper(Optional<DisplayAreaHelper> h);
 
         @BindsInstance
-        Builder setTaskSurfaceHelper(Optional<TaskSurfaceHelper> t);
-
-        @BindsInstance
         Builder setRecentTasks(Optional<RecentTasks> r);
 
         @BindsInstance
-        Builder setCompatUI(Optional<CompatUI> s);
-
-        @BindsInstance
-        Builder setDragAndDrop(Optional<DragAndDrop> d);
-
-        @BindsInstance
         Builder setBackAnimation(Optional<BackAnimation> b);
 
         SysUIComponent build();
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/WMComponent.java b/packages/SystemUI/src/com/android/systemui/dagger/WMComponent.java
index b6003e9..78a45f9 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/WMComponent.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/WMComponent.java
@@ -23,26 +23,22 @@
 
 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;
 import com.android.wm.shell.common.annotations.ShellMainThread;
-import com.android.wm.shell.compatui.CompatUI;
 import com.android.wm.shell.dagger.TvWMShellModule;
 import com.android.wm.shell.dagger.WMShellModule;
 import com.android.wm.shell.dagger.WMSingleton;
 import com.android.wm.shell.displayareahelper.DisplayAreaHelper;
-import com.android.wm.shell.draganddrop.DragAndDrop;
-import com.android.wm.shell.hidedisplaycutout.HideDisplayCutout;
 import com.android.wm.shell.onehanded.OneHanded;
 import com.android.wm.shell.pip.Pip;
 import com.android.wm.shell.recents.RecentTasks;
 import com.android.wm.shell.splitscreen.SplitScreen;
 import com.android.wm.shell.startingsurface.StartingSurface;
 import com.android.wm.shell.sysui.ShellInterface;
-import com.android.wm.shell.tasksurfacehelper.TaskSurfaceHelper;
 import com.android.wm.shell.transition.ShellTransitions;
 
 import java.util.Optional;
@@ -79,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();
@@ -104,9 +107,6 @@
     Optional<Bubbles> getBubbles();
 
     @WMSingleton
-    Optional<HideDisplayCutout> getHideDisplayCutout();
-
-    @WMSingleton
     Optional<TaskViewFactory> getTaskViewFactory();
 
     @WMSingleton
@@ -119,17 +119,8 @@
     Optional<DisplayAreaHelper> getDisplayAreaHelper();
 
     @WMSingleton
-    Optional<TaskSurfaceHelper> getTaskSurfaceHelper();
-
-    @WMSingleton
     Optional<RecentTasks> getRecentTasks();
 
     @WMSingleton
-    Optional<CompatUI> getCompatUI();
-
-    @WMSingleton
-    Optional<DragAndDrop> getDragAndDrop();
-
-    @WMSingleton
     Optional<BackAnimation> getBackAnimation();
 }
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..a566984 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
 
@@ -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/media/MediaControlPanel.java b/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java
index aeff2d4..012d766 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java
@@ -682,9 +682,22 @@
             Drawable artwork;
             boolean isArtworkBound;
             Icon artworkIcon = data.getArtwork();
+            WallpaperColors wallpaperColors = null;
             if (artworkIcon != null) {
-                WallpaperColors wallpaperColors = WallpaperColors
-                        .fromBitmap(artworkIcon.getBitmap());
+                if (artworkIcon.getType() == Icon.TYPE_BITMAP
+                        || artworkIcon.getType() == Icon.TYPE_ADAPTIVE_BITMAP) {
+                    // Avoids extra processing if this is already a valid bitmap
+                    wallpaperColors = WallpaperColors
+                            .fromBitmap(artworkIcon.getBitmap());
+                } else {
+                    Drawable artworkDrawable = artworkIcon.loadDrawable(mContext);
+                    if (artworkDrawable != null) {
+                        wallpaperColors = WallpaperColors
+                                .fromDrawable(artworkIcon.loadDrawable(mContext));
+                    }
+                }
+            }
+            if (wallpaperColors != null) {
                 mutableColorScheme = new ColorScheme(wallpaperColors, true, Style.CONTENT);
                 Drawable albumArt = getScaledBackground(artworkIcon, width, height);
                 GradientDrawable gradient = (GradientDrawable) mContext
diff --git a/packages/SystemUI/src/com/android/systemui/people/widget/LaunchConversationActivity.java b/packages/SystemUI/src/com/android/systemui/people/widget/LaunchConversationActivity.java
index e1b97a4..20c6c556 100644
--- a/packages/SystemUI/src/com/android/systemui/people/widget/LaunchConversationActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/people/widget/LaunchConversationActivity.java
@@ -21,6 +21,7 @@
 import android.content.Intent;
 import android.content.pm.LauncherApps;
 import android.os.Bundle;
+import android.os.RemoteException;
 import android.os.ServiceManager;
 import android.os.UserHandle;
 import android.os.UserManager;
@@ -34,6 +35,7 @@
 import com.android.internal.logging.UiEventLoggerImpl;
 import com.android.internal.statusbar.IStatusBarService;
 import com.android.internal.statusbar.NotificationVisibility;
+import com.android.systemui.dagger.qualifiers.Background;
 import com.android.systemui.people.PeopleSpaceUtils;
 import com.android.systemui.statusbar.CommandQueue;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
@@ -43,6 +45,7 @@
 import com.android.wm.shell.bubbles.Bubble;
 
 import java.util.Optional;
+import java.util.concurrent.Executor;
 
 import javax.inject.Inject;
 
@@ -58,6 +61,7 @@
     private boolean mIsForTesting;
     private IStatusBarService mIStatusBarService;
     private CommandQueue mCommandQueue;
+    private Executor mBgExecutor;
     private Bubble mBubble;
     private NotificationEntry mEntryToBubble;
 
@@ -67,7 +71,8 @@
             CommonNotifCollection commonNotifCollection,
             Optional<BubblesManager> bubblesManagerOptional,
             UserManager userManager,
-            CommandQueue commandQueue
+            CommandQueue commandQueue,
+            @Background Executor bgExecutor
     ) {
         super();
         mVisibilityProvider = visibilityProvider;
@@ -91,6 +96,7 @@
                 mCommandQueue.removeCallback(this);
             }
         });
+        mBgExecutor = bgExecutor;
     }
 
     @Override
@@ -172,34 +178,36 @@
             return;
         }
 
-        try {
-            if (mIStatusBarService == null || mCommonNotifCollection == null) {
-                if (DEBUG) {
-                    Log.d(TAG, "Skipping clear notification: null services, key: " + notifKey);
-                }
-                return;
+        if (mIStatusBarService == null || mCommonNotifCollection == null) {
+            if (DEBUG) {
+                Log.d(TAG, "Skipping clear notification: null services, key: " + notifKey);
             }
-
-            NotificationEntry entry = mCommonNotifCollection.getEntry(notifKey);
-            if (entry == null || entry.getRanking() == null) {
-                if (DEBUG) {
-                    Log.d(TAG, "Skipping clear notification: NotificationEntry or its Ranking"
-                            + " is null, key: " + notifKey);
-                }
-                return;
-            }
-
-            NotificationVisibility notifVisibility = mVisibilityProvider.obtain(entry, true);
-            int rank = notifVisibility.rank;
-
-            if (DEBUG) Log.d(TAG, "Clearing notification, key: " + notifKey + ", rank: " + rank);
-            mIStatusBarService.onNotificationClear(
-                    packageName, userHandle.getIdentifier(), notifKey,
-                    NotificationStats.DISMISSAL_OTHER,
-                    NotificationStats.DISMISS_SENTIMENT_POSITIVE, notifVisibility);
-        } catch (Exception e) {
-            Log.e(TAG, "Exception cancelling notification:" + e);
+            return;
         }
+
+        NotificationEntry entry = mCommonNotifCollection.getEntry(notifKey);
+        if (entry == null || entry.getRanking() == null) {
+            if (DEBUG) {
+                Log.d(TAG, "Skipping clear notification: NotificationEntry or its Ranking"
+                        + " is null, key: " + notifKey);
+            }
+            return;
+        }
+
+        NotificationVisibility notifVisibility = mVisibilityProvider.obtain(entry, true);
+        int rank = notifVisibility.rank;
+
+        if (DEBUG) Log.d(TAG, "Clearing notification, key: " + notifKey + ", rank: " + rank);
+        mBgExecutor.execute(() -> {
+            try {
+                mIStatusBarService.onNotificationClear(
+                        packageName, userHandle.getIdentifier(), notifKey,
+                        NotificationStats.DISMISSAL_OTHER,
+                        NotificationStats.DISMISS_SENTIMENT_POSITIVE, notifVisibility);
+            } catch (RemoteException e) {
+                Log.e(TAG, "Exception cancelling notification:" + e);
+            }
+        });
     }
 
     @VisibleForTesting
diff --git a/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingService.java b/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingService.java
index 5bb3413..a837cbb 100644
--- a/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingService.java
+++ b/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingService.java
@@ -29,6 +29,7 @@
 import android.media.MediaRecorder;
 import android.net.Uri;
 import android.os.Bundle;
+import android.os.Handler;
 import android.os.IBinder;
 import android.os.RemoteException;
 import android.os.UserHandle;
@@ -40,6 +41,8 @@
 import com.android.internal.logging.UiEventLogger;
 import com.android.systemui.R;
 import com.android.systemui.dagger.qualifiers.LongRunning;
+import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.screenrecord.ScreenMediaRecorder.ScreenMediaRecorderListener;
 import com.android.systemui.settings.UserContextProvider;
 import com.android.systemui.statusbar.phone.KeyguardDismissUtil;
 
@@ -51,9 +54,10 @@
 /**
  * A service which records the device screen and optionally microphone input.
  */
-public class RecordingService extends Service implements MediaRecorder.OnInfoListener {
+public class RecordingService extends Service implements ScreenMediaRecorderListener {
     public static final int REQUEST_CODE = 2;
 
+    private static final int USER_ID_NOT_SPECIFIED = -1;
     private static final int NOTIFICATION_RECORDING_ID = 4274;
     private static final int NOTIFICATION_PROCESSING_ID = 4275;
     private static final int NOTIFICATION_VIEW_ID = 4273;
@@ -73,6 +77,7 @@
 
     private final RecordingController mController;
     private final KeyguardDismissUtil mKeyguardDismissUtil;
+    private final Handler mMainHandler;
     private ScreenRecordingAudioSource mAudioSource;
     private boolean mShowTaps;
     private boolean mOriginalShowTaps;
@@ -84,10 +89,12 @@
 
     @Inject
     public RecordingService(RecordingController controller, @LongRunning Executor executor,
-            UiEventLogger uiEventLogger, NotificationManager notificationManager,
+            @Main Handler handler, UiEventLogger uiEventLogger,
+            NotificationManager notificationManager,
             UserContextProvider userContextTracker, KeyguardDismissUtil keyguardDismissUtil) {
         mController = controller;
         mLongExecutor = executor;
+        mMainHandler = handler;
         mUiEventLogger = uiEventLogger;
         mNotificationManager = notificationManager;
         mUserContextTracker = userContextTracker;
@@ -138,6 +145,7 @@
 
                 mRecorder = new ScreenMediaRecorder(
                         mUserContextTracker.getUserContext(),
+                        mMainHandler,
                         currentUserId,
                         mAudioSource,
                         this
@@ -166,14 +174,8 @@
                 }
                 // Check user ID - we may be getting a stop intent after user switch, in which case
                 // we want to post the notifications for that user, which is NOT current user
-                int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1);
-                if (userId == -1) {
-                    userId = mUserContextTracker.getUserContext().getUserId();
-                }
-                Log.d(TAG, "notifying for user " + userId);
-                stopRecording(userId);
-                mNotificationManager.cancel(NOTIFICATION_RECORDING_ID);
-                stopSelf();
+                int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, USER_ID_NOT_SPECIFIED);
+                stopService(userId);
                 break;
 
             case ACTION_SHARE:
@@ -378,15 +380,39 @@
         return builder.build();
     }
 
-    private void stopRecording(int userId) {
+    private void stopService() {
+        stopService(USER_ID_NOT_SPECIFIED);
+    }
+
+    private void stopService(int userId) {
+        if (userId == USER_ID_NOT_SPECIFIED) {
+            userId = mUserContextTracker.getUserContext().getUserId();
+        }
+        Log.d(TAG, "notifying for user " + userId);
         setTapsVisible(mOriginalShowTaps);
         if (getRecorder() != null) {
-            getRecorder().end();
-            saveRecording(userId);
+            try {
+                getRecorder().end();
+                saveRecording(userId);
+            } catch (RuntimeException exception) {
+                // RuntimeException could happen if the recording stopped immediately after starting
+                // let's release the recorder and delete all temporary files in this case
+                getRecorder().release();
+                showErrorToast(R.string.screenrecord_start_error);
+                Log.e(TAG, "stopRecording called, but there was an error when ending"
+                        + "recording");
+                exception.printStackTrace();
+            } catch (Throwable throwable) {
+                // Something unexpected happen, SystemUI will crash but let's delete
+                // the temporary files anyway
+                getRecorder().release();
+                throw new RuntimeException(throwable);
+            }
         } else {
             Log.e(TAG, "stopRecording called, but recorder was null");
         }
         updateState(false);
+        stopSelf();
     }
 
     private void saveRecording(int userId) {
@@ -446,4 +472,12 @@
         Log.d(TAG, "Media recorder info: " + what);
         onStartCommand(getStopIntent(this), 0, 0);
     }
+
+    @Override
+    public void onStopped() {
+        if (mController.isRecording()) {
+            Log.d(TAG, "Stopping recording because the system requested the stop");
+            stopService();
+        }
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenMediaRecorder.java b/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenMediaRecorder.java
index 2133cf6..d098b4b 100644
--- a/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenMediaRecorder.java
+++ b/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenMediaRecorder.java
@@ -40,6 +40,7 @@
 import android.media.projection.MediaProjection;
 import android.media.projection.MediaProjectionManager;
 import android.net.Uri;
+import android.os.Handler;
 import android.os.IBinder;
 import android.os.RemoteException;
 import android.os.ServiceManager;
@@ -51,16 +52,19 @@
 import android.view.WindowManager;
 
 import java.io.File;
+import java.io.Closeable;
 import java.io.IOException;
 import java.io.OutputStream;
 import java.nio.file.Files;
 import java.text.SimpleDateFormat;
+import java.util.ArrayList;
 import java.util.Date;
+import java.util.List;
 
 /**
  * Recording screen and mic/internal audio
  */
-public class ScreenMediaRecorder {
+public class ScreenMediaRecorder extends MediaProjection.Callback {
     private static final int TOTAL_NUM_TRACKS = 1;
     private static final int VIDEO_FRAME_RATE = 30;
     private static final int VIDEO_FRAME_RATE_TO_RESOLUTION_RATIO = 6;
@@ -81,14 +85,16 @@
     private ScreenRecordingMuxer mMuxer;
     private ScreenInternalAudioRecorder mAudio;
     private ScreenRecordingAudioSource mAudioSource;
+    private final Handler mHandler;
 
     private Context mContext;
-    MediaRecorder.OnInfoListener mListener;
+    ScreenMediaRecorderListener mListener;
 
-    public ScreenMediaRecorder(Context context,
+    public ScreenMediaRecorder(Context context, Handler handler,
             int user, ScreenRecordingAudioSource audioSource,
-            MediaRecorder.OnInfoListener listener) {
+            ScreenMediaRecorderListener listener) {
         mContext = context;
+        mHandler = handler;
         mUser = user;
         mListener = listener;
         mAudioSource = audioSource;
@@ -105,6 +111,7 @@
         IBinder projection = proj.asBinder();
         mMediaProjection = new MediaProjection(mContext,
                 IMediaProjection.Stub.asInterface(projection));
+        mMediaProjection.registerCallback(this, mHandler);
 
         File cacheDir = mContext.getCacheDir();
         cacheDir.mkdirs();
@@ -162,10 +169,15 @@
                 metrics.densityDpi,
                 DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR,
                 mInputSurface,
-                null,
-                null);
+                new VirtualDisplay.Callback() {
+                    @Override
+                    public void onStopped() {
+                        onStop();
+                    }
+                },
+                mHandler);
 
-        mMediaRecorder.setOnInfoListener(mListener);
+        mMediaRecorder.setOnInfoListener((mr, what, extra) -> mListener.onInfo(mr, what, extra));
         if (mAudioSource == INTERNAL ||
                 mAudioSource == MIC_AND_INTERNAL) {
             mTempAudioFile = File.createTempFile("temp", ".aac",
@@ -259,21 +271,34 @@
     }
 
     /**
-     * End screen recording
+     * End screen recording, throws an exception if stopping recording failed
      */
-    void end() {
-        mMediaRecorder.stop();
-        mMediaRecorder.release();
-        mInputSurface.release();
-        mVirtualDisplay.release();
-        mMediaProjection.stop();
+    void end() throws IOException {
+        Closer closer = new Closer();
+
+        // MediaRecorder might throw RuntimeException if stopped immediately after starting
+        // We should remove the recording in this case as it will be invalid
+        closer.register(mMediaRecorder::stop);
+        closer.register(mMediaRecorder::release);
+        closer.register(mInputSurface::release);
+        closer.register(mVirtualDisplay::release);
+        closer.register(mMediaProjection::stop);
+        closer.register(this::stopInternalAudioRecording);
+
+        closer.close();
+
         mMediaRecorder = null;
         mMediaProjection = null;
-        stopInternalAudioRecording();
 
         Log.d(TAG, "end recording");
     }
 
+    @Override
+    public void onStop() {
+        Log.d(TAG, "The system notified about stopping the projection");
+        mListener.onStopped();
+    }
+
     private void stopInternalAudioRecording() {
         if (mAudioSource == INTERNAL || mAudioSource == MIC_AND_INTERNAL) {
             mAudio.end();
@@ -337,6 +362,18 @@
     }
 
     /**
+     * Release the resources without saving the data
+     */
+    protected void release() {
+        if (mTempVideoFile != null) {
+            mTempVideoFile.delete();
+        }
+        if (mTempAudioFile != null) {
+            mTempAudioFile.delete();
+        }
+    }
+
+    /**
     * Object representing the recording
     */
     public class SavedRecording {
@@ -362,4 +399,66 @@
             return mThumbnailBitmap;
         }
     }
+
+    interface ScreenMediaRecorderListener {
+        /**
+         * Called to indicate an info or a warning during recording.
+         * See {@link MediaRecorder.OnInfoListener} for the full description.
+         */
+        void onInfo(MediaRecorder mr, int what, int extra);
+
+        /**
+         * Called when the recording stopped by the system.
+         * For example, this might happen when doing partial screen sharing of an app
+         * and the app that is being captured is closed.
+         */
+        void onStopped();
+    }
+
+    /**
+     * Allows to register multiple {@link Closeable} objects and close them all by calling
+     * {@link Closer#close}. If there is an exception thrown during closing of one
+     * of the registered closeables it will continue trying closing the rest closeables.
+     * If there are one or more exceptions thrown they will be re-thrown at the end.
+     * In case of multiple exceptions only the first one will be thrown and all the rest
+     * will be printed.
+     */
+    private static class Closer implements Closeable {
+        private final List<Closeable> mCloseables = new ArrayList<>();
+
+        void register(Closeable closeable) {
+            mCloseables.add(closeable);
+        }
+
+        @Override
+        public void close() throws IOException {
+            Throwable throwable = null;
+
+            for (int i = 0; i < mCloseables.size(); i++) {
+                Closeable closeable = mCloseables.get(i);
+
+                try {
+                    closeable.close();
+                } catch (Throwable e) {
+                    if (throwable == null) {
+                        throwable = e;
+                    } else {
+                        e.printStackTrace();
+                    }
+                }
+            }
+
+            if (throwable != null) {
+                if (throwable instanceof IOException) {
+                    throw (IOException) throwable;
+                }
+
+                if (throwable instanceof RuntimeException) {
+                    throw (RuntimeException) throwable;
+                }
+
+                throw (Error) throwable;
+            }
+        }
+    }
 }
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/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
index bab92ba..61d1963 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
@@ -189,7 +189,6 @@
 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;
@@ -1038,6 +1037,7 @@
         }
 
         mTapAgainViewController.init();
+        mLargeScreenShadeHeaderController.init();
         mKeyguardUnfoldTransition.ifPresent(u -> u.setup(mView));
         mNotificationPanelUnfoldAnimationController.ifPresent(controller ->
                 controller.setup(mNotificationContainerParent));
@@ -1143,7 +1143,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();
 
@@ -2417,7 +2417,7 @@
                 : getExpandedFraction();
         mLargeScreenShadeHeaderController.setShadeExpandedFraction(shadeExpandedFraction);
         mLargeScreenShadeHeaderController.setQsExpandedFraction(qsExpansionFraction);
-        mLargeScreenShadeHeaderController.setShadeExpanded(mQsVisible);
+        mLargeScreenShadeHeaderController.setQsVisible(mQsVisible);
     }
 
     private void onStackYChanged(boolean shouldAnimate) {
diff --git a/packages/SystemUI/src/com/android/systemui/shade/transition/ShadeTransitionController.kt b/packages/SystemUI/src/com/android/systemui/shade/transition/ShadeTransitionController.kt
index 1c4911d..58acfb4 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/transition/ShadeTransitionController.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/transition/ShadeTransitionController.kt
@@ -7,6 +7,8 @@
 import com.android.systemui.dump.DumpManager
 import com.android.systemui.plugins.qs.QS
 import com.android.systemui.shade.NotificationPanelViewController
+import com.android.systemui.statusbar.StatusBarState
+import com.android.systemui.statusbar.SysuiStatusBarStateController
 import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController
 import com.android.systemui.statusbar.phone.panelstate.PanelExpansionChangeEvent
 import com.android.systemui.statusbar.phone.panelstate.PanelExpansionStateManager
@@ -27,7 +29,8 @@
     private val context: Context,
     private val splitShadeOverScrollerFactory: SplitShadeOverScroller.Factory,
     private val noOpOverScroller: NoOpOverScroller,
-    private val scrimShadeTransitionController: ScrimShadeTransitionController
+    private val scrimShadeTransitionController: ScrimShadeTransitionController,
+    private val statusBarStateController: SysuiStatusBarStateController,
 ) {
 
     lateinit var notificationPanelViewController: NotificationPanelViewController
@@ -43,7 +46,7 @@
     }
     private val shadeOverScroller: ShadeOverScroller
         get() =
-            if (inSplitShade && propertiesInitialized()) {
+            if (inSplitShade && isScreenUnlocked() && propertiesInitialized()) {
                 splitShadeOverScroller
             } else {
                 noOpOverScroller
@@ -90,6 +93,7 @@
             """
             ShadeTransitionController:
                 inSplitShade: $inSplitShade
+                isScreenUnlocked: ${isScreenUnlocked()}
                 currentPanelState: ${currentPanelState?.panelStateToString()}
                 lastPanelExpansionChangeEvent: $lastPanelExpansionChangeEvent
                 qs.isInitialized: ${this::qs.isInitialized}
@@ -97,4 +101,7 @@
                 nssl.isInitialized: ${this::notificationStackScrollLayoutController.isInitialized}
             """.trimIndent())
     }
+
+    private fun isScreenUnlocked() =
+        statusBarStateController.currentOrUpcomingState == StatusBarState.SHADE
 }
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/NotificationEntryManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java
index c86bf93..852d5f7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java
@@ -41,6 +41,7 @@
 import com.android.internal.statusbar.IStatusBarService;
 import com.android.internal.statusbar.NotificationVisibility;
 import com.android.systemui.Dumpable;
+import com.android.systemui.dagger.qualifiers.Background;
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.statusbar.NotificationLifetimeExtender;
 import com.android.systemui.statusbar.NotificationListener;
@@ -73,6 +74,7 @@
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
+import java.util.concurrent.Executor;
 
 import dagger.Lazy;
 
@@ -113,6 +115,7 @@
     private final IStatusBarService mStatusBarService;
     private final NotifLiveDataStoreImpl mNotifLiveDataStore;
     private final DumpManager mDumpManager;
+    private final Executor mBgExecutor;
 
     private final Set<NotificationEntry> mAllNotifications = new ArraySet<>();
     private final Set<NotificationEntry> mReadOnlyAllNotifications =
@@ -159,7 +162,8 @@
             LeakDetector leakDetector,
             IStatusBarService statusBarService,
             NotifLiveDataStoreImpl notifLiveDataStore,
-            DumpManager dumpManager
+            DumpManager dumpManager,
+            @Background Executor bgExecutor
     ) {
         mLogger = logger;
         mGroupManager = groupManager;
@@ -170,6 +174,7 @@
         mStatusBarService = statusBarService;
         mNotifLiveDataStore = notifLiveDataStore;
         mDumpManager = dumpManager;
+        mBgExecutor = bgExecutor;
     }
 
     /** Once called, the NEM will start processing notification events from system server. */
@@ -566,17 +571,19 @@
     private void sendNotificationRemovalToServer(
             StatusBarNotification notification,
             DismissedByUserStats dismissedByUserStats) {
-        try {
-            mStatusBarService.onNotificationClear(
-                    notification.getPackageName(),
-                    notification.getUser().getIdentifier(),
-                    notification.getKey(),
-                    dismissedByUserStats.dismissalSurface,
-                    dismissedByUserStats.dismissalSentiment,
-                    dismissedByUserStats.notificationVisibility);
-        } catch (RemoteException ex) {
-            // system process is dead if we're here.
-        }
+        mBgExecutor.execute(() -> {
+            try {
+                mStatusBarService.onNotificationClear(
+                        notification.getPackageName(),
+                        notification.getUser().getIdentifier(),
+                        notification.getKey(),
+                        dismissedByUserStats.dismissalSurface,
+                        dismissedByUserStats.dismissalSentiment,
+                        dismissedByUserStats.notificationVisibility);
+            } catch (RemoteException ex) {
+                // system process is dead if we're here.
+            }
+        });
     }
 
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java
index 2c85fdd..351a4be 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java
@@ -70,6 +70,7 @@
 import com.android.internal.statusbar.IStatusBarService;
 import com.android.systemui.Dumpable;
 import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.dagger.qualifiers.Background;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.dump.LogBufferEulogizer;
@@ -112,6 +113,7 @@
 import java.util.Objects;
 import java.util.Queue;
 import java.util.Set;
+import java.util.concurrent.Executor;
 import java.util.concurrent.TimeUnit;
 
 import javax.inject.Inject;
@@ -146,6 +148,7 @@
     private final NotifPipelineFlags mNotifPipelineFlags;
     private final NotifCollectionLogger mLogger;
     private final Handler mMainHandler;
+    private final Executor mBgExecutor;
     private final LogBufferEulogizer mEulogizer;
     private final DumpManager mDumpManager;
 
@@ -174,6 +177,7 @@
             NotifPipelineFlags notifPipelineFlags,
             NotifCollectionLogger logger,
             @Main Handler mainHandler,
+            @Background Executor bgExecutor,
             LogBufferEulogizer logBufferEulogizer,
             DumpManager dumpManager) {
         mStatusBarService = statusBarService;
@@ -181,6 +185,7 @@
         mNotifPipelineFlags = notifPipelineFlags;
         mLogger = logger;
         mMainHandler = mainHandler;
+        mBgExecutor = bgExecutor;
         mEulogizer = logBufferEulogizer;
         mDumpManager = dumpManager;
     }
@@ -294,18 +299,20 @@
             entriesToLocallyDismiss.add(entry);
             if (!isCanceled(entry)) {
                 // send message to system server if this notification hasn't already been cancelled
-                try {
-                    mStatusBarService.onNotificationClear(
-                            entry.getSbn().getPackageName(),
-                            entry.getSbn().getUser().getIdentifier(),
-                            entry.getSbn().getKey(),
-                            stats.dismissalSurface,
-                            stats.dismissalSentiment,
-                            stats.notificationVisibility);
-                } catch (RemoteException e) {
-                    // system process is dead if we're here.
-                    mLogger.logRemoteExceptionOnNotificationClear(entry, e);
-                }
+                mBgExecutor.execute(() -> {
+                    try {
+                        mStatusBarService.onNotificationClear(
+                                entry.getSbn().getPackageName(),
+                                entry.getSbn().getUser().getIdentifier(),
+                                entry.getSbn().getKey(),
+                                stats.dismissalSurface,
+                                stats.dismissalSentiment,
+                                stats.notificationVisibility);
+                    } catch (RemoteException e) {
+                        // system process is dead if we're here.
+                        mLogger.logRemoteExceptionOnNotificationClear(entry, e);
+                    }
+                });
             }
         }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java
index 4956a276..bf08fc7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java
@@ -128,7 +128,8 @@
             LeakDetector leakDetector,
             IStatusBarService statusBarService,
             NotifLiveDataStoreImpl notifLiveDataStore,
-            DumpManager dumpManager) {
+            DumpManager dumpManager,
+            @Background Executor bgExecutor) {
         return new NotificationEntryManager(
                 logger,
                 groupManager,
@@ -138,7 +139,8 @@
                 leakDetector,
                 statusBarService,
                 notifLiveDataStore,
-                dumpManager);
+                dumpManager,
+                bgExecutor);
     }
 
     /** Provides an instance of {@link NotificationGutsManager} */
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 fff7b6a..2d6d846 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
@@ -1709,7 +1709,7 @@
             // Wrap the animation controller to dismiss the shade and set
             // mIsLaunchingActivityOverLockscreen during the animation.
             ActivityLaunchAnimator.Controller delegate = wrapAnimationController(
-                    animationController, dismissShade);
+                    animationController, dismissShade, /* isLaunchForActivity= */ true);
             controller = new DelegateLaunchAnimatorController(delegate) {
                 @Override
                 public void onIntentStarted(boolean willAnimate) {
@@ -2457,7 +2457,7 @@
                         true /* isActivityIntent */);
         ActivityLaunchAnimator.Controller animController =
                 animationController != null ? wrapAnimationController(animationController,
-                        dismissShade) : null;
+                        dismissShade, /* isLaunchForActivity= */ true) : null;
 
         // If we animate, we will dismiss the shade only once the animation is done. This is taken
         // care of by the StatusBarLaunchAnimationController.
@@ -2535,9 +2535,25 @@
                 willLaunchResolverActivity, deferred /* deferred */, animate);
     }
 
+    /**
+     * Return a {@link ActivityLaunchAnimator.Controller} wrapping {@code animationController} so
+     * that:
+     *  - if it launches in the notification shade window and {@code dismissShade} is true, then
+     *    the shade will be instantly dismissed at the end of the animation.
+     *  - if it launches in status bar window, it will make the status bar window match the device
+     *    size during the animation (that way, the animation won't be clipped by the status bar
+     *    size).
+     *
+     * @param animationController the controller that is wrapped and will drive the main animation.
+     * @param dismissShade whether the notification shade will be dismissed at the end of the
+     *                     animation. This is ignored if {@code animationController} is not
+     *                     animating in the shade window.
+     * @param isLaunchForActivity whether the launch is for an activity.
+     */
     @Nullable
     private ActivityLaunchAnimator.Controller wrapAnimationController(
-            ActivityLaunchAnimator.Controller animationController, boolean dismissShade) {
+            ActivityLaunchAnimator.Controller animationController, boolean dismissShade,
+            boolean isLaunchForActivity) {
         View rootView = animationController.getLaunchContainer().getRootView();
 
         Optional<ActivityLaunchAnimator.Controller> controllerFromStatusBar =
@@ -2551,7 +2567,7 @@
             // If the view is not in the status bar, then we are animating a view in the shade.
             // We have to make sure that we collapse it when the animation ends or is cancelled.
             return new StatusBarLaunchAnimatorController(animationController, this,
-                    true /* isLaunchForActivity */);
+                    isLaunchForActivity);
         }
 
         return animationController;
@@ -4083,8 +4099,9 @@
                 // We wrap animationCallback with a StatusBarLaunchAnimatorController so that the
                 // shade is collapsed after the animation (or when it is cancelled, aborted, etc).
                 ActivityLaunchAnimator.Controller controller =
-                        animationController != null ? new StatusBarLaunchAnimatorController(
-                                animationController, this, intent.isActivity()) : null;
+                        animationController != null ? wrapAnimationController(
+                                animationController, /* dismissShade= */ true, intent.isActivity())
+                                : null;
 
                 mActivityLaunchAnimator.startPendingIntentWithAnimation(
                         controller, animate, intent.getCreatorPackage(),
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/NotificationIconContainer.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java
index 2dc3261..a2140c6ab 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java
@@ -13,7 +13,6 @@
  * See the License for the specific language governing permissions and
  * limitations under the License
  */
-
 package com.android.systemui.statusbar.phone;
 
 import static com.android.systemui.statusbar.phone.HeadsUpAppearanceController.CONTENT_FADE_DELAY;
@@ -31,6 +30,7 @@
 import android.util.Property;
 import android.view.ContextThemeWrapper;
 import android.view.View;
+import android.view.ViewGroup;
 import android.view.animation.Interpolator;
 
 import androidx.annotation.VisibleForTesting;
@@ -40,7 +40,6 @@
 import com.android.settingslib.Utils;
 import com.android.systemui.R;
 import com.android.systemui.animation.Interpolators;
-import com.android.systemui.statusbar.AlphaOptimizedFrameLayout;
 import com.android.systemui.statusbar.StatusBarIconView;
 import com.android.systemui.statusbar.notification.stack.AnimationFilter;
 import com.android.systemui.statusbar.notification.stack.AnimationProperties;
@@ -54,7 +53,7 @@
  * A container for notification icons. It handles overflowing icons properly and positions them
  * correctly on the screen.
  */
-public class NotificationIconContainer extends AlphaOptimizedFrameLayout {
+public class NotificationIconContainer extends ViewGroup {
     /**
      * A float value indicating how much before the overflow start the icons should transform into
      * a dot. A value of 0 means that they are exactly at the end and a value of 1 means it starts
@@ -232,6 +231,31 @@
     }
 
     @Override
+    public boolean hasOverlappingRendering() {
+        // Does the same as "AlphaOptimizedFrameLayout".
+        return false;
+    }
+
+    @Override
+    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+        final int childCount = getChildCount();
+        final int maxVisibleIcons = getMaxVisibleIcons(childCount);
+        final int width = MeasureSpec.getSize(widthMeasureSpec);
+        final int childWidthSpec = MeasureSpec.makeMeasureSpec(width, MeasureSpec.UNSPECIFIED);
+        int totalWidth = (int) (getActualPaddingStart() + getActualPaddingEnd());
+        for (int i = 0; i < childCount; i++) {
+            View child = getChildAt(i);
+            measureChild(child, childWidthSpec, heightMeasureSpec);
+            if (i <= maxVisibleIcons) {
+                totalWidth += child.getMeasuredWidth();
+            }
+        }
+        final int measuredWidth = resolveSize(totalWidth, widthMeasureSpec);
+        final int measuredHeight = MeasureSpec.getSize(heightMeasureSpec);
+        setMeasuredDimension(measuredWidth, measuredHeight);
+    }
+
+    @Override
     protected void onLayout(boolean changed, int l, int t, int r, int b) {
         float centerY = getHeight() / 2.0f;
         // we layout all our children on the left at the top
@@ -408,8 +432,7 @@
         float translationX = getActualPaddingStart();
         int firstOverflowIndex = -1;
         int childCount = getChildCount();
-        int maxVisibleIcons = mOnLockScreen ? MAX_ICONS_ON_AOD :
-                mIsStaticLayout ? MAX_STATIC_ICONS : childCount;
+        int maxVisibleIcons = getMaxVisibleIcons(childCount);
         float layoutEnd = getLayoutEnd();
         mVisualOverflowStart = 0;
         mFirstVisibleIconState = null;
@@ -493,6 +516,11 @@
         }
     }
 
+    private int getMaxVisibleIcons(int childCount) {
+        return mOnLockScreen ? MAX_ICONS_ON_AOD :
+                mIsStaticLayout ? MAX_STATIC_ICONS : childCount;
+    }
+
     private float getLayoutEnd() {
         return getActualWidth() - getActualPaddingEnd();
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarTransitions.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarTransitions.java
index 2052ee6..15c6dcf 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarTransitions.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarTransitions.java
@@ -31,7 +31,7 @@
 
     private final float mIconAlphaWhenOpaque;
 
-    private View mLeftSide, mStatusIcons, mBattery;
+    private View mStartSide, mStatusIcons, mBattery;
     private Animator mCurrentAnimation;
 
     /**
@@ -41,7 +41,7 @@
         super(backgroundView, R.drawable.status_background);
         final Resources res = statusBarView.getContext().getResources();
         mIconAlphaWhenOpaque = res.getFraction(R.dimen.status_bar_icon_drawing_alpha, 1, 1);
-        mLeftSide = statusBarView.findViewById(R.id.status_bar_left_side);
+        mStartSide = statusBarView.findViewById(R.id.status_bar_start_side_except_heads_up);
         mStatusIcons = statusBarView.findViewById(R.id.statusIcons);
         mBattery = statusBarView.findViewById(R.id.battery);
         applyModeBackground(-1, getMode(), false /*animate*/);
@@ -75,7 +75,7 @@
     }
 
     private void applyMode(int mode, boolean animate) {
-        if (mLeftSide == null) return; // pre-init
+        if (mStartSide == null) return; // pre-init
         float newAlpha = getNonBatteryClockAlphaFor(mode);
         float newAlphaBC = getBatteryClockAlpha(mode);
         if (mCurrentAnimation != null) {
@@ -84,7 +84,7 @@
         if (animate) {
             AnimatorSet anims = new AnimatorSet();
             anims.playTogether(
-                    animateTransitionTo(mLeftSide, newAlpha),
+                    animateTransitionTo(mStartSide, newAlpha),
                     animateTransitionTo(mStatusIcons, newAlpha),
                     animateTransitionTo(mBattery, newAlphaBC)
                     );
@@ -94,7 +94,7 @@
             anims.start();
             mCurrentAnimation = anims;
         } else {
-            mLeftSide.setAlpha(newAlpha);
+            mStartSide.setAlpha(newAlpha);
             mStatusIcons.setAlpha(newAlpha);
             mBattery.setAlpha(newAlphaBC);
         }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt
index 9da2ef73..f9c4c8f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt
@@ -21,7 +21,6 @@
 import android.view.View
 import android.view.ViewGroup
 import android.view.ViewTreeObserver
-
 import com.android.systemui.R
 import com.android.systemui.shared.animation.UnfoldMoveFromCenterAnimator
 import com.android.systemui.statusbar.phone.userswitcher.StatusBarUserSwitcherController
@@ -32,9 +31,7 @@
 import com.android.systemui.util.ViewController
 import com.android.systemui.util.kotlin.getOrNull
 import com.android.systemui.util.view.ViewUtil
-
 import java.util.Optional
-
 import javax.inject.Inject
 import javax.inject.Named
 
@@ -58,8 +55,8 @@
     override fun onViewAttached() {
         if (moveFromCenterAnimationController == null) return
 
-        val statusBarLeftSide: View = mView.findViewById(R.id.status_bar_left_side)
-        val systemIconArea: ViewGroup = mView.findViewById(R.id.system_icon_area)
+        val statusBarLeftSide: View = mView.findViewById(R.id.status_bar_start_side_except_heads_up)
+        val systemIconArea: ViewGroup = mView.findViewById(R.id.status_bar_end_side_content)
 
         val viewsToAnimate = arrayOf(
             statusBarLeftSide,
@@ -126,11 +123,11 @@
     class StatusBarViewsCenterProvider : UnfoldMoveFromCenterAnimator.ViewCenterProvider {
         override fun getViewCenter(view: View, outPoint: Point) =
             when (view.id) {
-                R.id.status_bar_left_side -> {
+                R.id.status_bar_start_side_except_heads_up -> {
                     // items aligned to the start, return start center point
                     getViewEdgeCenter(view, outPoint, isStart = true)
                 }
-                R.id.system_icon_area -> {
+                R.id.status_bar_end_side_content -> {
                     // items aligned to the end, return end center point
                     getViewEdgeCenter(view, outPoint, isStart = false)
                 }
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 0e7da81..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";
@@ -102,7 +110,7 @@
     private final KeyguardStateController mKeyguardStateController;
     private final NotificationPanelViewController mNotificationPanelViewController;
     private final NetworkController mNetworkController;
-    private LinearLayout mSystemIconArea;
+    private LinearLayout mEndSideContent;
     private View mClockView;
     private View mOngoingCallChip;
     private View mNotificationIconAreaInner;
@@ -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);
@@ -232,16 +251,16 @@
         mDarkIconManager.setShouldLog(true);
         updateBlockedIcons();
         mStatusBarIconController.addIconGroup(mDarkIconManager);
-        mSystemIconArea = mStatusBar.findViewById(R.id.system_icon_area);
+        mEndSideContent = mStatusBar.findViewById(R.id.status_bar_end_side_content);
         mClockView = mStatusBar.findViewById(R.id.clock);
         mOngoingCallChip = mStatusBar.findViewById(R.id.ongoing_call_chip);
-        showSystemIconArea(false);
+        showEndSideContent(false);
         showClock(false);
         initEmergencyCryptkeeperText();
         initOperatorName();
         initNotificationIconArea();
         mSystemEventAnimator =
-                new StatusBarSystemEventAnimator(mSystemIconArea, getResources());
+                new StatusBarSystemEventAnimator(mEndSideContent, getResources());
         mCarrierConfigTracker.addCallback(mCarrierConfigCallback);
         mCarrierConfigTracker.addDefaultDataSubscriptionChangedListener(mDefaultDataListener);
     }
@@ -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. */
@@ -370,10 +396,10 @@
         mDisabled2 = state2;
         if ((diff1 & DISABLE_SYSTEM_INFO) != 0 || ((diff2 & DISABLE2_SYSTEM_ICONS) != 0)) {
             if ((state1 & DISABLE_SYSTEM_INFO) != 0 || ((state2 & DISABLE2_SYSTEM_ICONS) != 0)) {
-                hideSystemIconArea(animate);
+                hideEndSideContent(animate);
                 hideOperatorName(animate);
             } else {
-                showSystemIconArea(animate);
+                showEndSideContent(animate);
                 showOperatorName(animate);
             }
         }
@@ -474,15 +500,15 @@
         return mStatusBarHideIconsForBouncerManager.getShouldHideStatusBarIconsForBouncer();
     }
 
-    private void hideSystemIconArea(boolean animate) {
-        animateHide(mSystemIconArea, animate);
+    private void hideEndSideContent(boolean animate) {
+        animateHide(mEndSideContent, animate);
     }
 
-    private void showSystemIconArea(boolean animate) {
+    private void showEndSideContent(boolean animate) {
         // Only show the system icon area if we are not currently animating
         int state = mAnimationScheduler.getAnimationState();
         if (state == IDLE || state == SHOWING_PERSISTENT_DOT) {
-            animateShow(mSystemIconArea, animate);
+            animateShow(mEndSideContent, animate);
         }
     }
 
@@ -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/BubblesManager.java b/packages/SystemUI/src/com/android/systemui/wmshell/BubblesManager.java
index e08a9d0..708a8ab 100644
--- a/packages/SystemUI/src/com/android/systemui/wmshell/BubblesManager.java
+++ b/packages/SystemUI/src/com/android/systemui/wmshell/BubblesManager.java
@@ -34,7 +34,6 @@
 import android.app.NotificationManager;
 import android.content.Context;
 import android.content.pm.UserInfo;
-import android.content.res.Configuration;
 import android.os.RemoteException;
 import android.os.ServiceManager;
 import android.os.UserHandle;
diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java b/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
index fa2af56..12597e0 100644
--- a/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
+++ b/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
@@ -54,10 +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.compatui.CompatUI;
-import com.android.wm.shell.draganddrop.DragAndDrop;
-import com.android.wm.shell.hidedisplaycutout.HideDisplayCutout;
 import com.android.wm.shell.nano.WmShellTraceProto;
 import com.android.wm.shell.onehanded.OneHanded;
 import com.android.wm.shell.onehanded.OneHandedEventCallback;
@@ -110,10 +106,6 @@
     private final Optional<Pip> mPipOptional;
     private final Optional<SplitScreen> mSplitScreenOptional;
     private final Optional<OneHanded> mOneHandedOptional;
-    private final Optional<HideDisplayCutout> mHideDisplayCutoutOptional;
-    private final Optional<ShellCommandHandler> mShellCommandHandler;
-    private final Optional<CompatUI> mCompatUIOptional;
-    private final Optional<DragAndDrop> mDragAndDropOptional;
 
     private final CommandQueue mCommandQueue;
     private final ConfigurationController mConfigurationController;
@@ -127,10 +119,7 @@
     private final Executor mSysUiMainExecutor;
 
     private boolean mIsSysUiStateValid;
-    private KeyguardUpdateMonitorCallback mSplitScreenKeyguardCallback;
-    private KeyguardUpdateMonitorCallback mPipKeyguardCallback;
     private KeyguardUpdateMonitorCallback mOneHandedKeyguardCallback;
-    private KeyguardStateController.Callback mCompatUIKeyguardCallback;
     private WakefulnessLifecycle.Observer mWakefulnessObserver;
 
     @Inject
@@ -139,10 +128,6 @@
             Optional<Pip> pipOptional,
             Optional<SplitScreen> splitScreenOptional,
             Optional<OneHanded> oneHandedOptional,
-            Optional<HideDisplayCutout> hideDisplayCutoutOptional,
-            Optional<ShellCommandHandler> shellCommandHandler,
-            Optional<CompatUI> sizeCompatUIOptional,
-            Optional<DragAndDrop> dragAndDropOptional,
             CommandQueue commandQueue,
             ConfigurationController configurationController,
             KeyguardStateController keyguardStateController,
@@ -164,12 +149,8 @@
         mPipOptional = pipOptional;
         mSplitScreenOptional = splitScreenOptional;
         mOneHandedOptional = oneHandedOptional;
-        mHideDisplayCutoutOptional = hideDisplayCutoutOptional;
         mWakefulnessLifecycle = wakefulnessLifecycle;
         mProtoTracer = protoTracer;
-        mShellCommandHandler = shellCommandHandler;
-        mCompatUIOptional = sizeCompatUIOptional;
-        mDragAndDropOptional = dragAndDropOptional;
         mUserInfoController = userInfoController;
         mSysUiMainExecutor = sysUiMainExecutor;
     }
@@ -185,6 +166,22 @@
             }
         });
 
+        // Subscribe to keyguard changes
+        mKeyguardStateController.addCallback(new KeyguardStateController.Callback() {
+            @Override
+            public void onKeyguardShowingChanged() {
+                mShell.onKeyguardVisibilityChanged(mKeyguardStateController.isShowing(),
+                        mKeyguardStateController.isOccluded(),
+                        mKeyguardStateController.isAnimatingBetweenKeyguardAndSurfaceBehind());
+            }
+        });
+        mKeyguardUpdateMonitor.registerCallback(new KeyguardUpdateMonitorCallback() {
+            @Override
+            public void onKeyguardDismissAnimationFinished() {
+                mShell.onKeyguardDismissAnimationFinished();
+            }
+        });
+
         // TODO: Consider piping config change and other common calls to a shell component to
         //  delegate internally
         mProtoTracer.add(this);
@@ -192,7 +189,6 @@
         mPipOptional.ifPresent(this::initPip);
         mSplitScreenOptional.ifPresent(this::initSplitScreen);
         mOneHandedOptional.ifPresent(this::initOneHanded);
-        mCompatUIOptional.ifPresent(this::initCompatUi);
     }
 
     @VisibleForTesting
@@ -204,20 +200,6 @@
             }
         });
 
-        mPipKeyguardCallback = new KeyguardUpdateMonitorCallback() {
-            @Override
-            public void onKeyguardVisibilityChanged(boolean showing) {
-                pip.onKeyguardVisibilityChanged(showing,
-                        mKeyguardStateController.isAnimatingBetweenKeyguardAndSurfaceBehind());
-            }
-
-            @Override
-            public void onKeyguardDismissAnimationFinished() {
-                pip.onKeyguardDismissAnimationFinished();
-            }
-        };
-        mKeyguardUpdateMonitor.registerCallback(mPipKeyguardCallback);
-
         mSysUiState.addCallback(sysUiStateFlag -> {
             mIsSysUiStateValid = (sysUiStateFlag & INVALID_SYSUI_STATE_MASK) == 0;
             pip.onSystemUiStateChanged(mIsSysUiStateValid, sysUiStateFlag);
@@ -230,14 +212,6 @@
 
     @VisibleForTesting
     void initSplitScreen(SplitScreen splitScreen) {
-        mSplitScreenKeyguardCallback = new KeyguardUpdateMonitorCallback() {
-            @Override
-            public void onKeyguardVisibilityChanged(boolean showing) {
-                splitScreen.onKeyguardVisibilityChanged(showing);
-            }
-        };
-        mKeyguardUpdateMonitor.registerCallback(mSplitScreenKeyguardCallback);
-
         mWakefulnessLifecycle.addObserver(new WakefulnessLifecycle.Observer() {
             @Override
             public void onFinishedWakingUp() {
@@ -283,14 +257,9 @@
             }
         });
 
+        // TODO: Either move into ShellInterface or register a receiver on the Shell side directly
         mOneHandedKeyguardCallback = new KeyguardUpdateMonitorCallback() {
             @Override
-            public void onKeyguardVisibilityChanged(boolean showing) {
-                oneHanded.onKeyguardVisibilityChanged(showing);
-                oneHanded.stopOneHanded();
-            }
-
-            @Override
             public void onUserSwitchComplete(int userId) {
                 oneHanded.onUserSwitch(userId);
             }
@@ -340,17 +309,6 @@
         });
     }
 
-    @VisibleForTesting
-    void initCompatUi(CompatUI sizeCompatUI) {
-        mCompatUIKeyguardCallback = new KeyguardStateController.Callback() {
-            @Override
-            public void onKeyguardShowingChanged() {
-                sizeCompatUI.onKeyguardShowingChanged(mKeyguardStateController.isShowing());
-            }
-        };
-        mKeyguardStateController.addCallback(mCompatUIKeyguardCallback);
-    }
-
     @Override
     public void writeToProto(SystemUiTraceProto proto) {
         if (proto.wmShell == null) {
@@ -363,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
@@ -372,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/systemui/controls/controller/ControlsControllerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsControllerImplTest.kt
index 3c7ea4f..c31fd82 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsControllerImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsControllerImplTest.kt
@@ -37,9 +37,14 @@
 import com.android.systemui.controls.management.ControlsListingController
 import com.android.systemui.controls.ui.ControlsUiController
 import com.android.systemui.dump.DumpManager
+import com.android.systemui.settings.UserFileManager
 import com.android.systemui.settings.UserTracker
 import com.android.systemui.util.concurrency.FakeExecutor
 import com.android.systemui.util.time.FakeSystemClock
+import com.google.common.truth.Truth.assertThat
+import java.io.File
+import java.util.Optional
+import java.util.function.Consumer
 import org.junit.After
 import org.junit.Assert.assertEquals
 import org.junit.Assert.assertFalse
@@ -50,20 +55,20 @@
 import org.junit.runner.RunWith
 import org.mockito.ArgumentCaptor
 import org.mockito.ArgumentMatchers
+import org.mockito.ArgumentMatchers.anyString
 import org.mockito.Captor
 import org.mockito.Mock
 import org.mockito.Mockito
 import org.mockito.Mockito.anyInt
-import org.mockito.Mockito.`when`
 import org.mockito.Mockito.inOrder
 import org.mockito.Mockito.mock
 import org.mockito.Mockito.never
 import org.mockito.Mockito.reset
+import org.mockito.Mockito.times
 import org.mockito.Mockito.verify
 import org.mockito.Mockito.verifyNoMoreInteractions
+import org.mockito.Mockito.`when`
 import org.mockito.MockitoAnnotations
-import java.util.Optional
-import java.util.function.Consumer
 
 @SmallTest
 @RunWith(AndroidTestingRunner::class)
@@ -85,6 +90,8 @@
     private lateinit var listingController: ControlsListingController
     @Mock(stubOnly = true)
     private lateinit var userTracker: UserTracker
+    @Mock
+    private lateinit var userFileManager: UserFileManager
 
     @Captor
     private lateinit var structureInfoCaptor: ArgumentCaptor<StructureInfo>
@@ -153,6 +160,9 @@
 
         canceller = DidRunRunnable()
         `when`(bindingController.bindAndLoad(any(), any())).thenReturn(canceller)
+        `when`(userFileManager.getFile(anyString(), anyInt())).thenReturn(mock(File::class.java))
+        `when`(userFileManager.getSharedPreferences(anyString(), anyInt(), anyInt()))
+            .thenReturn(context.getSharedPreferences("test", Context.MODE_PRIVATE))
 
         controller = ControlsControllerImpl(
                 wrapper,
@@ -161,6 +171,7 @@
                 bindingController,
                 listingController,
                 broadcastDispatcher,
+                userFileManager,
                 Optional.of(persistenceWrapper),
                 mock(DumpManager::class.java),
                 userTracker
@@ -217,6 +228,7 @@
                 bindingController,
                 listingController,
                 broadcastDispatcher,
+                userFileManager,
                 Optional.of(persistenceWrapper),
                 mock(DumpManager::class.java),
                 userTracker
@@ -911,6 +923,14 @@
 
         assertTrue(controller.getFavoritesForStructure(TEST_COMPONENT_2, TEST_STRUCTURE).isEmpty())
     }
+
+    @Test
+    fun testUserStructure() {
+        val userStructure = UserStructure(context, context.user, userFileManager)
+        verify(userFileManager, times(2))
+            .getFile(ControlsFavoritePersistenceWrapper.FILE_NAME, context.user.identifier)
+        assertThat(userStructure.file).isNotNull()
+    }
 }
 
 private class DidRunRunnable() : Runnable {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MediaControlPanelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/MediaControlPanelTest.kt
index c13c30b..1785022 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaControlPanelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaControlPanelTest.kt
@@ -561,6 +561,19 @@
     }
 
     @Test
+    fun bindAlbumView_artUsesResource() {
+        val albumArt = Icon.createWithResource(context, R.drawable.ic_android)
+        val state = mediaData.copy(artwork = albumArt)
+
+        player.attachPlayer(viewHolder)
+        player.bindPlayer(state, PACKAGE)
+        bgExecutor.runAllReady()
+        mainExecutor.runAllReady()
+
+        verify(albumView).setImageDrawable(any(Drawable::class.java))
+    }
+
+    @Test
     fun bindAlbumView_setAfterExecutors() {
         val bmp = Bitmap.createBitmap(10, 10, Bitmap.Config.ARGB_8888)
         val canvas = Canvas(bmp)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/people/widget/LaunchConversationActivityTest.java b/packages/SystemUI/tests/src/com/android/systemui/people/widget/LaunchConversationActivityTest.java
index 99f21ad..c8ebd12 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/people/widget/LaunchConversationActivityTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/people/widget/LaunchConversationActivityTest.java
@@ -48,6 +48,8 @@
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection;
 import com.android.systemui.statusbar.notification.collection.render.NotificationVisibilityProvider;
+import com.android.systemui.util.concurrency.FakeExecutor;
+import com.android.systemui.util.time.FakeSystemClock;
 import com.android.systemui.wmshell.BubblesManager;
 import com.android.wm.shell.bubbles.Bubble;
 
@@ -106,6 +108,9 @@
 
     private Intent mIntent;
 
+    private FakeSystemClock mFakeSystemClock = new FakeSystemClock();
+    private FakeExecutor mBgExecutor = new FakeExecutor(mFakeSystemClock);
+
     @Before
     public void setUp() throws Exception {
         MockitoAnnotations.initMocks(this);
@@ -114,7 +119,8 @@
                 mNotifCollection,
                 Optional.of(mBubblesManager),
                 mUserManager,
-                mCommandQueue
+                mCommandQueue,
+                mBgExecutor
         );
         verify(mCommandQueue, times(1)).addCallback(mCallbacksCaptor.capture());
 
@@ -193,6 +199,7 @@
         // Ensure callback removed
         verify(mCommandQueue).removeCallback(any());
         // Clear the notification for bubbles.
+        FakeExecutor.exhaustExecutors(mBgExecutor);
         verify(mIStatusBarService, times(1)).onNotificationClear(any(),
                 anyInt(), any(), anyInt(), anyInt(), mNotificationVisibilityCaptor.capture());
         // Do not select the bubble.
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenrecord/RecordingServiceTest.java b/packages/SystemUI/tests/src/com/android/systemui/screenrecord/RecordingServiceTest.java
index 91cafea..b05d9a3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenrecord/RecordingServiceTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenrecord/RecordingServiceTest.java
@@ -16,18 +16,21 @@
 
 package com.android.systemui.screenrecord;
 
+import static org.junit.Assert.assertThrows;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.Mockito.doNothing;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 
 import android.app.Notification;
 import android.app.NotificationManager;
 import android.content.Intent;
+import android.os.Handler;
 import android.os.RemoteException;
 import android.testing.AndroidTestingRunner;
 
@@ -66,6 +69,8 @@
     @Mock
     private Executor mExecutor;
     @Mock
+    private Handler mHandler;
+    @Mock
     private UserContextProvider mUserContextTracker;
     private KeyguardDismissUtil mKeyguardDismissUtil = new KeyguardDismissUtil() {
         public void executeWhenUnlocked(ActivityStarter.OnDismissAction action,
@@ -79,8 +84,8 @@
     @Before
     public void setUp() throws Exception {
         MockitoAnnotations.initMocks(this);
-        mRecordingService = Mockito.spy(new RecordingService(mController, mExecutor, mUiEventLogger,
-                mNotificationManager, mUserContextTracker, mKeyguardDismissUtil));
+        mRecordingService = Mockito.spy(new RecordingService(mController, mExecutor, mHandler,
+                mUiEventLogger, mNotificationManager, mUserContextTracker, mKeyguardDismissUtil));
 
         // Return actual context info
         doReturn(mContext).when(mRecordingService).getApplicationContext();
@@ -143,4 +148,54 @@
         // Then the state is set to not recording
         verify(mController).updateState(false);
     }
+
+    @Test
+    public void testOnSystemRequestedStop_recordingInProgress_endsRecording() throws IOException {
+        doReturn(true).when(mController).isRecording();
+
+        mRecordingService.onStopped();
+
+        verify(mScreenMediaRecorder).end();
+    }
+
+    @Test
+    public void testOnSystemRequestedStop_recordingInProgress_updatesState() {
+        doReturn(true).when(mController).isRecording();
+
+        mRecordingService.onStopped();
+
+        verify(mController).updateState(false);
+    }
+
+    @Test
+    public void testOnSystemRequestedStop_recordingIsNotInProgress_doesNotEndRecording()
+            throws IOException {
+        doReturn(false).when(mController).isRecording();
+
+        mRecordingService.onStopped();
+
+        verify(mScreenMediaRecorder, never()).end();
+    }
+
+    @Test
+    public void testOnSystemRequestedStop_recorderEndThrowsRuntimeException_releasesRecording()
+            throws IOException {
+        doReturn(true).when(mController).isRecording();
+        doThrow(new RuntimeException()).when(mScreenMediaRecorder).end();
+
+        mRecordingService.onStopped();
+
+        verify(mScreenMediaRecorder).release();
+    }
+
+    @Test
+    public void testOnSystemRequestedStop_recorderEndThrowsOOMError_releasesRecording()
+            throws IOException {
+        doReturn(true).when(mController).isRecording();
+        doThrow(new OutOfMemoryError()).when(mScreenMediaRecorder).end();
+
+        assertThrows(Throwable.class, () -> mRecordingService.onStopped());
+
+        verify(mScreenMediaRecorder).release();
+    }
 }
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..30c1b25 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java
@@ -143,7 +143,6 @@
 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;
@@ -1182,11 +1181,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
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/transition/ShadeTransitionControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/transition/ShadeTransitionControllerTest.kt
index d6faaee..b6f8326 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/transition/ShadeTransitionControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/transition/ShadeTransitionControllerTest.kt
@@ -7,6 +7,8 @@
 import com.android.systemui.dump.DumpManager
 import com.android.systemui.plugins.qs.QS
 import com.android.systemui.shade.NotificationPanelViewController
+import com.android.systemui.statusbar.StatusBarState
+import com.android.systemui.statusbar.SysuiStatusBarStateController
 import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController
 import com.android.systemui.statusbar.phone.panelstate.PanelExpansionChangeEvent
 import com.android.systemui.statusbar.phone.panelstate.PanelExpansionStateManager
@@ -19,6 +21,7 @@
 import org.mockito.Mockito.reset
 import org.mockito.Mockito.verify
 import org.mockito.Mockito.verifyZeroInteractions
+import org.mockito.Mockito.`when` as whenever
 import org.mockito.MockitoAnnotations
 
 @RunWith(AndroidTestingRunner::class)
@@ -32,6 +35,7 @@
     @Mock private lateinit var splitShadeOverScroller: SplitShadeOverScroller
     @Mock private lateinit var scrimShadeTransitionController: ScrimShadeTransitionController
     @Mock private lateinit var dumpManager: DumpManager
+    @Mock private lateinit var statusBarStateController: SysuiStatusBarStateController
 
     private lateinit var controller: ShadeTransitionController
 
@@ -50,7 +54,9 @@
                 context,
                 splitShadeOverScrollerFactory = { _, _ -> splitShadeOverScroller },
                 noOpOverScroller,
-                scrimShadeTransitionController)
+                scrimShadeTransitionController,
+                statusBarStateController,
+            )
 
         // Resetting as they are notified upon initialization.
         reset(noOpOverScroller, splitShadeOverScroller)
@@ -80,6 +86,45 @@
     }
 
     @Test
+    fun onPanelStateChanged_inSplitShade_onKeyguard_forwardsToNoOpOverScroller() {
+        initLateProperties()
+        enableSplitShade()
+        setOnKeyguard()
+
+        startPanelExpansion()
+
+        verify(noOpOverScroller).onPanelStateChanged(STATE_OPENING)
+        verify(noOpOverScroller).onDragDownAmountChanged(DEFAULT_DRAG_DOWN_AMOUNT)
+        verifyZeroInteractions(splitShadeOverScroller)
+    }
+
+    @Test
+    fun onPanelStateChanged_inSplitShade_onLockedShade_forwardsToNoOpOverScroller() {
+        initLateProperties()
+        enableSplitShade()
+        setOnLockedShade()
+
+        startPanelExpansion()
+
+        verify(noOpOverScroller).onPanelStateChanged(STATE_OPENING)
+        verify(noOpOverScroller).onDragDownAmountChanged(DEFAULT_DRAG_DOWN_AMOUNT)
+        verifyZeroInteractions(splitShadeOverScroller)
+    }
+
+    @Test
+    fun onPanelExpansionChanged_inSplitShade_onUnlockedShade_forwardsToSplitShadeOverScroller() {
+        initLateProperties()
+        enableSplitShade()
+        setOnUnlockedShade()
+
+        startPanelExpansion()
+
+        verify(splitShadeOverScroller).onPanelStateChanged(STATE_OPENING)
+        verify(splitShadeOverScroller).onDragDownAmountChanged(DEFAULT_DRAG_DOWN_AMOUNT)
+        verifyZeroInteractions(noOpOverScroller)
+    }
+
+    @Test
     fun onPanelStateChanged_notInSplitShade_forwardsToNoOpOverScroller() {
         initLateProperties()
         disableSplitShade()
@@ -129,6 +174,23 @@
         )
     }
 
+    private fun setOnKeyguard() {
+        setShadeState(StatusBarState.KEYGUARD)
+    }
+
+    private fun setOnLockedShade() {
+        setShadeState(StatusBarState.SHADE_LOCKED)
+    }
+
+    private fun setOnUnlockedShade() {
+        setShadeState(StatusBarState.SHADE)
+    }
+
+    private fun setShadeState(state: Int) {
+        whenever(statusBarStateController.state).thenReturn(state)
+        whenever(statusBarStateController.currentOrUpcomingState).thenReturn(state)
+    }
+
     companion object {
         private const val DEFAULT_DRAG_DOWN_AMOUNT = 123f
         private val DEFAULT_EXPANSION_EVENT =
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/ClockRegistryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/ClockRegistryTest.kt
new file mode 100644
index 0000000..aaa2357
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/ClockRegistryTest.kt
@@ -0,0 +1,209 @@
+/*
+ * 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.shared.clocks
+
+import org.mockito.Mockito.`when` as whenever
+import android.content.Context
+import android.content.ContentResolver
+import android.graphics.drawable.Drawable
+import android.os.Handler
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.plugins.PluginListener
+import com.android.systemui.shared.plugins.PluginManager
+import com.android.systemui.util.mockito.argumentCaptor
+import com.android.systemui.util.mockito.eq
+import junit.framework.Assert.assertEquals
+import junit.framework.Assert.fail
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito.verify
+import org.mockito.junit.MockitoJUnit
+
+@RunWith(AndroidTestingRunner::class)
+@SmallTest
+class ClockRegistryTest : SysuiTestCase() {
+
+    @JvmField @Rule val mockito = MockitoJUnit.rule()
+    @Mock private lateinit var mockContext: Context
+    @Mock private lateinit var mockPluginManager: PluginManager
+    @Mock private lateinit var mockClock: Clock
+    @Mock private lateinit var mockThumbnail: Drawable
+    @Mock private lateinit var mockHandler: Handler
+    @Mock private lateinit var mockContentResolver: ContentResolver
+    private lateinit var pluginListener: PluginListener<ClockProviderPlugin>
+    private lateinit var registry: ClockRegistry
+
+    private var settingValue: String = ""
+
+    companion object {
+        private fun failFactory(): Clock {
+            fail("Unexpected call to createClock")
+            return null!!
+        }
+
+        private fun failThumbnail(): Drawable? {
+            fail("Unexpected call to getThumbnail")
+            return null
+        }
+    }
+
+    private class FakeClockPlugin : ClockProviderPlugin {
+        private val metadata = mutableListOf<ClockMetadata>()
+        private val createCallbacks = mutableMapOf<ClockId, () -> Clock>()
+        private val thumbnailCallbacks = mutableMapOf<ClockId, () -> Drawable?>()
+
+        override fun getClocks() = metadata
+        override fun createClock(id: ClockId): Clock = createCallbacks[id]!!()
+        override fun getClockThumbnail(id: ClockId): Drawable? = thumbnailCallbacks[id]!!()
+
+        fun addClock(
+            id: ClockId,
+            name: String,
+            create: () -> Clock = ::failFactory,
+            getThumbnail: () -> Drawable? = ::failThumbnail
+        ) {
+            metadata.add(ClockMetadata(id, name))
+            createCallbacks[id] = create
+            thumbnailCallbacks[id] = getThumbnail
+        }
+    }
+
+    @Before
+    fun setUp() {
+        whenever(mockContext.contentResolver).thenReturn(mockContentResolver)
+
+        val captor = argumentCaptor<PluginListener<ClockProviderPlugin>>()
+        registry = object : ClockRegistry(mockContext, mockPluginManager, mockHandler) {
+            override var currentClockId: ClockId
+                get() = settingValue
+                set(value) { settingValue = value }
+        }
+        verify(mockPluginManager).addPluginListener(captor.capture(),
+            eq(ClockProviderPlugin::class.java))
+        pluginListener = captor.value
+    }
+
+    @Test
+    fun pluginRegistration_CorrectState() {
+        val plugin1 = FakeClockPlugin()
+        plugin1.addClock("clock_1", "clock 1")
+        plugin1.addClock("clock_2", "clock 2")
+
+        val plugin2 = FakeClockPlugin()
+        plugin2.addClock("clock_3", "clock 3")
+        plugin2.addClock("clock_4", "clock 4")
+
+        pluginListener.onPluginConnected(plugin1, mockContext)
+        pluginListener.onPluginConnected(plugin2, mockContext)
+        val list = registry.getClocks()
+        assertEquals(list, listOf(
+            ClockMetadata("clock_1", "clock 1"),
+            ClockMetadata("clock_2", "clock 2"),
+            ClockMetadata("clock_3", "clock 3"),
+            ClockMetadata("clock_4", "clock 4")
+        ))
+    }
+
+    @Test
+    fun clockIdConflict_ErrorWithoutCrash() {
+        val plugin1 = FakeClockPlugin()
+        plugin1.addClock("clock_1", "clock 1", { mockClock }, { mockThumbnail })
+        plugin1.addClock("clock_2", "clock 2", { mockClock }, { mockThumbnail })
+
+        val plugin2 = FakeClockPlugin()
+        plugin2.addClock("clock_1", "clock 1")
+        plugin2.addClock("clock_2", "clock 2")
+
+        pluginListener.onPluginConnected(plugin1, mockContext)
+        pluginListener.onPluginConnected(plugin2, mockContext)
+        val list = registry.getClocks()
+        assertEquals(list, listOf(
+            ClockMetadata("clock_1", "clock 1"),
+            ClockMetadata("clock_2", "clock 2")
+        ))
+
+        assertEquals(registry.createExampleClock("clock_1"), mockClock)
+        assertEquals(registry.createExampleClock("clock_2"), mockClock)
+        assertEquals(registry.getClockThumbnail("clock_1"), mockThumbnail)
+        assertEquals(registry.getClockThumbnail("clock_2"), mockThumbnail)
+    }
+
+    @Test
+    fun createCurrentClock_pluginConnected() {
+        val plugin1 = FakeClockPlugin()
+        plugin1.addClock("clock_1", "clock 1")
+        plugin1.addClock("clock_2", "clock 2")
+
+        settingValue = "clock_3"
+        val plugin2 = FakeClockPlugin()
+        plugin2.addClock("clock_3", "clock 3", { mockClock })
+        plugin2.addClock("clock_4", "clock 4")
+
+        pluginListener.onPluginConnected(plugin1, mockContext)
+        pluginListener.onPluginConnected(plugin2, mockContext)
+
+        val clock = registry.createCurrentClock()
+        assertEquals(clock, mockClock)
+    }
+
+    @Test
+    fun createDefaultClock_pluginDisconnected() {
+        val plugin1 = FakeClockPlugin()
+        plugin1.addClock(DEFAULT_CLOCK_ID, "default", { mockClock })
+        plugin1.addClock("clock_2", "clock 2")
+
+        settingValue = "clock_3"
+        val plugin2 = FakeClockPlugin()
+        plugin2.addClock("clock_3", "clock 3")
+        plugin2.addClock("clock_4", "clock 4")
+
+        pluginListener.onPluginConnected(plugin1, mockContext)
+        pluginListener.onPluginConnected(plugin2, mockContext)
+        pluginListener.onPluginDisconnected(plugin2)
+
+        val clock = registry.createCurrentClock()
+        assertEquals(clock, mockClock)
+    }
+
+    @Test
+    fun pluginRemoved_clockChanged() {
+        val plugin1 = FakeClockPlugin()
+        plugin1.addClock("clock_1", "clock 1")
+        plugin1.addClock("clock_2", "clock 2")
+
+        settingValue = "clock_3"
+        val plugin2 = FakeClockPlugin()
+        plugin2.addClock("clock_3", "clock 3", { mockClock })
+        plugin2.addClock("clock_4", "clock 4")
+
+        pluginListener.onPluginConnected(plugin1, mockContext)
+        pluginListener.onPluginConnected(plugin2, mockContext)
+
+        var changeCallCount = 0
+        registry.registerClockChangeListener({ changeCallCount++ })
+
+        pluginListener.onPluginDisconnected(plugin1)
+        assertEquals(0, changeCallCount)
+
+        pluginListener.onPluginDisconnected(plugin2)
+        assertEquals(1, changeCallCount)
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationEntryManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationEntryManagerTest.java
index 16b0376..aeef6b0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationEntryManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationEntryManagerTest.java
@@ -19,6 +19,8 @@
 import static android.app.NotificationManager.IMPORTANCE_DEFAULT;
 import static android.service.notification.NotificationListenerService.NOTIFICATION_CHANNEL_OR_GROUP_UPDATED;
 import static android.service.notification.NotificationListenerService.REASON_CANCEL;
+import static android.service.notification.NotificationStats.DISMISSAL_SHADE;
+import static android.service.notification.NotificationStats.DISMISS_SENTIMENT_NEUTRAL;
 
 import static com.android.systemui.statusbar.notification.NotificationEntryManager.UNDEFINED_DISMISS_REASON;
 
@@ -41,6 +43,7 @@
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
 import static org.mockito.Mockito.when;
 
 import android.app.ActivityManager;
@@ -50,6 +53,7 @@
 import android.content.Intent;
 import android.graphics.drawable.Icon;
 import android.os.Handler;
+import android.os.RemoteException;
 import android.os.UserHandle;
 import android.service.notification.NotificationListenerService.Ranking;
 import android.service.notification.NotificationListenerService.RankingMap;
@@ -91,7 +95,9 @@
 import com.android.systemui.statusbar.notification.row.RowInflaterTask;
 import com.android.systemui.statusbar.policy.DeviceProvisionedController;
 import com.android.systemui.statusbar.policy.HeadsUpManager;
+import com.android.systemui.util.concurrency.FakeExecutor;
 import com.android.systemui.util.leak.LeakDetector;
+import com.android.systemui.util.time.FakeSystemClock;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -138,9 +144,14 @@
     @Mock private NotificationMediaManager mNotificationMediaManager;
     @Mock private NotificationRowBinder mNotificationRowBinder;
     @Mock private NotificationListener mNotificationListener;
+    @Mock private IStatusBarService mStatusBarService;
+
+    private FakeSystemClock mFakeSystemClock = new FakeSystemClock();
+    private FakeExecutor mBgExecutor = new FakeExecutor(mFakeSystemClock);
 
     private int mId;
     private NotificationEntry mEntry;
+    private DismissedByUserStats mStats;
     private StatusBarNotification mSbn;
     private NotificationEntryManager mEntryManager;
 
@@ -191,6 +202,7 @@
                 Handler.createAsync(TestableLooper.get(this).getLooper()));
 
         mEntry = createNotification();
+        mStats = defaultStats(mEntry);
         mSbn = mEntry.getSbn();
 
         when(mNotifPipelineFlags.isNewPipelineEnabled()).thenReturn(false);
@@ -201,9 +213,10 @@
                 () -> mNotificationRowBinder,
                 () -> mRemoteInputManager,
                 mLeakDetector,
-                mock(IStatusBarService.class),
+                mStatusBarService,
                 NotifLiveDataStoreMocksKt.createNotifLiveDataStoreImplMock(),
-                mock(DumpManager.class)
+                mock(DumpManager.class),
+                mBgExecutor
         );
         mEntryManager.initialize(
                 mNotificationListener,
@@ -316,6 +329,31 @@
     }
 
     @Test
+    public void testPerformRemoveNotification_sendRemovalToServer() throws RemoteException {
+        // GIVEN an entry manager with a notification
+        mEntryManager.addActiveNotificationForTest(mEntry);
+
+        // GIVEN interceptor that doesn't intercept
+        when(mRemoveInterceptor.onNotificationRemoveRequested(
+                eq(mEntry.getKey()), argThat(matchEntryOnKey()), anyInt()))
+                .thenReturn(false);
+
+        // WHEN the notification entry is removed
+        mEntryManager.performRemoveNotification(mSbn, mStats, REASON_CANCEL);
+
+        // THEN notification removal is sent to the server
+        FakeExecutor.exhaustExecutors(mBgExecutor);
+        verify(mStatusBarService).onNotificationClear(
+                mSbn.getPackageName(),
+                mSbn.getUser().getIdentifier(),
+                mSbn.getKey(),
+                mStats.dismissalSurface,
+                mStats.dismissalSentiment,
+                mStats.notificationVisibility);
+        verifyNoMoreInteractions(mStatusBarService);
+    }
+
+    @Test
     public void testRemoveNotification_onEntryRemoveNotFiredIfEntryDoesntExist() {
 
         mEntryManager.removeNotification("not_a_real_key", mRankingMap, UNDEFINED_DISMISS_REASON);
@@ -573,23 +611,6 @@
                 any(NotificationVisibility.class), anyBoolean(), eq(UNDEFINED_DISMISS_REASON));
     }
 
-    private NotificationEntry createNotification() {
-        Notification.Builder n = new Notification.Builder(mContext, "id")
-                .setSmallIcon(R.drawable.ic_person)
-                .setContentTitle("Title")
-                .setContentText("Text");
-
-        return new NotificationEntryBuilder()
-                .setPkg(TEST_PACKAGE_NAME)
-                .setOpPkg(TEST_PACKAGE_NAME)
-                .setUid(TEST_UID)
-                .setId(mId++)
-                .setNotification(n.build())
-                .setChannel(new NotificationChannel("id", "", IMPORTANCE_DEFAULT))
-                .setUser(new UserHandle(ActivityManager.getCurrentUser()))
-                .build();
-    }
-
     /* Tests annexed from NotificationDataTest go here */
 
     @Test
@@ -713,4 +734,28 @@
             return mManagedNotifs.contains(notificationKey);
         }
     }
+
+    private NotificationEntry createNotification() {
+        Notification.Builder n = new Notification.Builder(mContext, "id")
+                .setSmallIcon(R.drawable.ic_person)
+                .setContentTitle("Title")
+                .setContentText("Text");
+
+        return new NotificationEntryBuilder()
+                .setPkg(TEST_PACKAGE_NAME)
+                .setOpPkg(TEST_PACKAGE_NAME)
+                .setUid(TEST_UID)
+                .setId(mId++)
+                .setNotification(n.build())
+                .setChannel(new NotificationChannel("id", "", IMPORTANCE_DEFAULT))
+                .setUser(new UserHandle(ActivityManager.getCurrentUser()))
+                .build();
+    }
+
+    private static DismissedByUserStats defaultStats(NotificationEntry entry) {
+        return new DismissedByUserStats(
+                DISMISSAL_SHADE,
+                DISMISS_SENTIMENT_NEUTRAL,
+                NotificationVisibility.obtain(entry.getKey(), 7, 2, true));
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifCollectionTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifCollectionTest.java
index ef763d9..4df99be 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifCollectionTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifCollectionTest.java
@@ -92,6 +92,7 @@
 import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionLogger;
 import com.android.systemui.statusbar.notification.collection.notifcollection.NotifDismissInterceptor;
 import com.android.systemui.statusbar.notification.collection.notifcollection.NotifLifetimeExtender;
+import com.android.systemui.util.concurrency.FakeExecutor;
 import com.android.systemui.util.time.FakeSystemClock;
 
 import org.junit.Before;
@@ -146,6 +147,7 @@
 
     private NoManSimulator mNoMan;
     private FakeSystemClock mClock = new FakeSystemClock();
+    private FakeExecutor mBgExecutor = new FakeExecutor(mClock);
 
     @Before
     public void setUp() {
@@ -162,6 +164,7 @@
                 mNotifPipelineFlags,
                 mLogger,
                 mMainHandler,
+                mBgExecutor,
                 mEulogizer,
                 mock(DumpManager.class));
         mCollection.attach(mGroupCoalescer);
@@ -461,6 +464,8 @@
         DismissedByUserStats stats = defaultStats(entry2);
         mCollection.dismissNotification(entry2, defaultStats(entry2));
 
+        FakeExecutor.exhaustExecutors(mBgExecutor);
+
         // THEN we send the dismissal to system server
         verify(mStatusBarService).onNotificationClear(
                 notif2.sbn.getPackageName(),
@@ -674,6 +679,8 @@
         mInterceptor1.onEndInterceptionCallback.onEndDismissInterception(mInterceptor1, entry,
                 stats);
 
+        FakeExecutor.exhaustExecutors(mBgExecutor);
+
         // THEN we send the dismissal to system server
         verify(mStatusBarService).onNotificationClear(
                 eq(notif.sbn.getPackageName()),
@@ -1211,6 +1218,7 @@
                         new Pair<>(entry2, defaultStats(entry2))));
 
         // THEN we send the dismissals to system server
+        FakeExecutor.exhaustExecutors(mBgExecutor);
         verify(mStatusBarService).onNotificationClear(
                 notif1.sbn.getPackageName(),
                 notif1.sbn.getUser().getIdentifier(),
@@ -1577,6 +1585,7 @@
 
         // WHEN finally dismissing
         onDismiss.run();
+        FakeExecutor.exhaustExecutors(mBgExecutor);
         verify(mStatusBarService).onNotificationClear(any(), anyInt(), eq(notifEvent.key),
                 anyInt(), anyInt(), any());
         verifyNoMoreInteractions(mStatusBarService);
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/notification/row/NotificationEntryManagerInflationTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationEntryManagerInflationTest.java
index 251ac7d..bf7549a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationEntryManagerInflationTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationEntryManagerInflationTest.java
@@ -194,7 +194,8 @@
                 mLeakDetector,
                 mock(IStatusBarService.class),
                 NotifLiveDataStoreMocksKt.createNotifLiveDataStoreImplMock(),
-                mock(DumpManager.class)
+                mock(DumpManager.class),
+                mBgExecutor
         );
         mEntryManager.initialize(
                 mNotificationListener,
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/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 3f14494..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);
@@ -124,7 +127,7 @@
 
         fragment.disable(DEFAULT_DISPLAY, 0, 0, false);
 
-        assertEquals(View.VISIBLE, getSystemIconAreaView().getVisibility());
+        assertEquals(View.VISIBLE, getEndSideContentView().getVisibility());
         assertEquals(View.VISIBLE, getClockView().getVisibility());
     }
 
@@ -134,11 +137,11 @@
 
         fragment.disable(DEFAULT_DISPLAY, StatusBarManager.DISABLE_SYSTEM_INFO, 0, false);
 
-        assertEquals(View.INVISIBLE, getSystemIconAreaView().getVisibility());
+        assertEquals(View.INVISIBLE, getEndSideContentView().getVisibility());
 
         fragment.disable(DEFAULT_DISPLAY, 0, 0, false);
 
-        assertEquals(View.VISIBLE, getSystemIconAreaView().getVisibility());
+        assertEquals(View.VISIBLE, getEndSideContentView().getVisibility());
     }
 
     @Test
@@ -234,7 +237,7 @@
 
         fragment.disable(DEFAULT_DISPLAY, 0, 0, false);
 
-        assertEquals(View.VISIBLE, getSystemIconAreaView().getVisibility());
+        assertEquals(View.VISIBLE, getEndSideContentView().getVisibility());
         assertEquals(View.VISIBLE, getClockView().getVisibility());
     }
 
@@ -246,7 +249,7 @@
 
         fragment.disable(DEFAULT_DISPLAY, 0, 0, false);
 
-        assertEquals(View.VISIBLE, getSystemIconAreaView().getVisibility());
+        assertEquals(View.VISIBLE, getEndSideContentView().getVisibility());
         assertEquals(View.VISIBLE, getClockView().getVisibility());
     }
 
@@ -257,12 +260,12 @@
         when(mNotificationPanelViewController.hasCustomClock()).thenReturn(true);
 
         // Make sure they start out as visible
-        assertEquals(View.VISIBLE, getSystemIconAreaView().getVisibility());
+        assertEquals(View.VISIBLE, getEndSideContentView().getVisibility());
         assertEquals(View.VISIBLE, getClockView().getVisibility());
 
         fragment.disable(DEFAULT_DISPLAY, 0, 0, false);
 
-        assertEquals(View.INVISIBLE, getSystemIconAreaView().getVisibility());
+        assertEquals(View.INVISIBLE, getEndSideContentView().getVisibility());
         assertEquals(View.GONE, getClockView().getVisibility());
     }
 
@@ -273,14 +276,14 @@
         when(mNotificationPanelViewController.hasCustomClock()).thenReturn(true);
 
         // Make sure they start out as visible
-        assertEquals(View.VISIBLE, getSystemIconAreaView().getVisibility());
+        assertEquals(View.VISIBLE, getEndSideContentView().getVisibility());
         assertEquals(View.VISIBLE, getClockView().getVisibility());
 
         fragment.onDozingChanged(true);
 
         // When this callback is triggered, we want to make sure the clock and system info
         // visibilities are recalculated. Since dozing=true, they shouldn't be visible.
-        assertEquals(View.INVISIBLE, getSystemIconAreaView().getVisibility());
+        assertEquals(View.INVISIBLE, getEndSideContentView().getVisibility());
         assertEquals(View.GONE, getClockView().getVisibility());
     }
 
@@ -386,7 +389,8 @@
                         ),
                 mOperatorNameViewControllerFactory,
                 mSecureSettings,
-                mExecutor);
+                mExecutor,
+                mDumpManager);
     }
 
     private void setUpDaggerComponent() {
@@ -419,7 +423,7 @@
         return mFragment.getView().findViewById(R.id.clock);
     }
 
-    private View getSystemIconAreaView() {
-        return mFragment.getView().findViewById(R.id.system_icon_area);
+    private View getEndSideContentView() {
+        return mFragment.getView().findViewById(R.id.status_bar_end_side_content);
     }
 }
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 2b37b9e..9c21366 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/WMShellTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/WMShellTest.java
@@ -24,22 +24,16 @@
 import androidx.test.runner.AndroidJUnit4;
 
 import com.android.keyguard.KeyguardUpdateMonitor;
-import com.android.keyguard.KeyguardUpdateMonitorCallback;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.keyguard.ScreenLifecycle;
 import com.android.systemui.keyguard.WakefulnessLifecycle;
 import com.android.systemui.model.SysUiState;
-import com.android.systemui.navigationbar.NavigationModeController;
 import com.android.systemui.statusbar.CommandQueue;
 import com.android.systemui.statusbar.policy.ConfigurationController;
 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.compatui.CompatUI;
-import com.android.wm.shell.draganddrop.DragAndDrop;
-import com.android.wm.shell.hidedisplaycutout.HideDisplayCutout;
 import com.android.wm.shell.onehanded.OneHanded;
 import com.android.wm.shell.onehanded.OneHandedEventCallback;
 import com.android.wm.shell.onehanded.OneHandedTransitionCallback;
@@ -71,32 +65,25 @@
     @Mock ConfigurationController mConfigurationController;
     @Mock KeyguardStateController mKeyguardStateController;
     @Mock KeyguardUpdateMonitor mKeyguardUpdateMonitor;
-    @Mock NavigationModeController mNavigationModeController;
     @Mock ScreenLifecycle mScreenLifecycle;
     @Mock SysUiState mSysUiState;
     @Mock Pip mPip;
     @Mock SplitScreen mSplitScreen;
     @Mock OneHanded mOneHanded;
-    @Mock HideDisplayCutout mHideDisplayCutout;
     @Mock WakefulnessLifecycle mWakefulnessLifecycle;
     @Mock ProtoTracer mProtoTracer;
-    @Mock ShellCommandHandler mShellCommandHandler;
-    @Mock CompatUI mCompatUI;
     @Mock UserInfoController mUserInfoController;
     @Mock ShellExecutor mSysUiMainExecutor;
-    @Mock DragAndDrop mDragAndDrop;
 
     @Before
     public void setUp() {
         MockitoAnnotations.initMocks(this);
 
         mWMShell = new WMShell(mContext, mShellInterface, Optional.of(mPip),
-                Optional.of(mSplitScreen), Optional.of(mOneHanded), Optional.of(mHideDisplayCutout),
-                Optional.of(mShellCommandHandler), Optional.of(mCompatUI),
-                Optional.of(mDragAndDrop),
-                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
@@ -107,27 +94,12 @@
     }
 
     @Test
-    public void initSplitScreen_registersCallbacks() {
-        mWMShell.initSplitScreen(mSplitScreen);
-
-        verify(mKeyguardUpdateMonitor).registerCallback(any(KeyguardUpdateMonitorCallback.class));
-    }
-
-    @Test
     public void initOneHanded_registersCallbacks() {
         mWMShell.initOneHanded(mOneHanded);
 
-        verify(mKeyguardUpdateMonitor).registerCallback(any(KeyguardUpdateMonitorCallback.class));
         verify(mCommandQueue).addCallback(any(CommandQueue.Callbacks.class));
         verify(mScreenLifecycle).addObserver(any(ScreenLifecycle.Observer.class));
         verify(mOneHanded).registerTransitionCallback(any(OneHandedTransitionCallback.class));
         verify(mOneHanded).registerEventCallback(any(OneHandedEventCallback.class));
     }
-
-    @Test
-    public void initCompatUI_registersCallbacks() {
-        mWMShell.initCompatUi(mCompatUI);
-
-        verify(mKeyguardStateController).addCallback(any(KeyguardStateController.Callback.class));
-    }
 }
diff --git a/services/contentcapture/Android.bp b/services/contentcapture/Android.bp
index 434f239..5392c2c 100644
--- a/services/contentcapture/Android.bp
+++ b/services/contentcapture/Android.bp
@@ -17,6 +17,9 @@
 java_library_static {
     name: "services.contentcapture",
     defaults: ["platform_service_defaults"],
-    srcs: [":services.contentcapture-sources"],
+    srcs: [
+        ":services.contentcapture-sources",
+        "java/**/*.logtags",
+    ],
     libs: ["services.core"],
 }
diff --git a/services/contentcapture/java/com/android/server/contentcapture/ContentCapturePerUserService.java b/services/contentcapture/java/com/android/server/contentcapture/ContentCapturePerUserService.java
index 41a7592..0428b23 100644
--- a/services/contentcapture/java/com/android/server/contentcapture/ContentCapturePerUserService.java
+++ b/services/contentcapture/java/com/android/server/contentcapture/ContentCapturePerUserService.java
@@ -60,6 +60,7 @@
 import android.service.voice.VoiceInteractionManagerInternal;
 import android.util.ArrayMap;
 import android.util.ArraySet;
+import android.util.EventLog;
 import android.util.Slog;
 import android.util.SparseArray;
 import android.util.SparseBooleanArray;
@@ -88,6 +89,11 @@
 
     private static final String TAG = ContentCapturePerUserService.class.getSimpleName();
 
+    private static final int EVENT_LOG_CONNECT_STATE_DIED = 0;
+    static final int EVENT_LOG_CONNECT_STATE_CONNECTED = 1;
+    static final int EVENT_LOG_CONNECT_STATE_DISCONNECTED = 2;
+
+
     @GuardedBy("mLock")
     private final SparseArray<ContentCaptureServerSession> mSessions = new SparseArray<>();
 
@@ -190,9 +196,13 @@
         Slog.w(TAG, "remote service died: " + service);
         synchronized (mLock) {
             mZombie = true;
+            ComponentName serviceComponent = getServiceComponentName();
             writeServiceEvent(
                     FrameworkStatsLog.CONTENT_CAPTURE_SERVICE_EVENTS__EVENT__ON_REMOTE_SERVICE_DIED,
-                    getServiceComponentName());
+                    serviceComponent);
+            EventLog.writeEvent(EventLogTags.CC_CONNECT_STATE_CHANGED, mUserId,
+                    serviceComponent != null ? serviceComponent.flattenToShortString() : "",
+                    EVENT_LOG_CONNECT_STATE_DIED);
         }
     }
 
@@ -614,11 +624,16 @@
                         ? "null_activities" : activities.size() + " activities") + ")"
                         + " for user " + mUserId);
             }
+            int packageCount = packages != null ? packages.size() : 0;
+            int activityCount = activities != null ? activities.size() : 0;
 
             ArraySet<String> oldList =
                     mMaster.mGlobalContentCaptureOptions.getWhitelistedPackages(mUserId);
+            EventLog.writeEvent(EventLogTags.CC_CURRENT_ALLOWLIST, mUserId, oldList.size());
 
             mMaster.mGlobalContentCaptureOptions.setWhitelist(mUserId, packages, activities);
+            EventLog.writeEvent(EventLogTags.CC_SET_ALLOWLIST, mUserId,
+                    packageCount, activityCount);
             writeSetWhitelistEvent(getServiceComponentName(), packages, activities);
 
             updateContentCaptureOptions(oldList);
@@ -699,12 +714,14 @@
         private void updateContentCaptureOptions(@Nullable ArraySet<String> oldList) {
             ArraySet<String> adding = mMaster.mGlobalContentCaptureOptions
                     .getWhitelistedPackages(mUserId);
+            EventLog.writeEvent(EventLogTags.CC_CURRENT_ALLOWLIST, mUserId, adding.size());
 
             if (oldList != null && adding != null) {
                 adding.removeAll(oldList);
             }
 
             int N = adding != null ? adding.size() : 0;
+            EventLog.writeEvent(EventLogTags.CC_UPDATE_OPTIONS, mUserId, N);
             for (int i = 0; i < N; i++) {
                 String packageName = adding.valueAt(i);
                 ContentCaptureOptions options = mMaster.mGlobalContentCaptureOptions
diff --git a/services/contentcapture/java/com/android/server/contentcapture/EventLogTags.logtags b/services/contentcapture/java/com/android/server/contentcapture/EventLogTags.logtags
new file mode 100644
index 0000000..6722b9e
--- /dev/null
+++ b/services/contentcapture/java/com/android/server/contentcapture/EventLogTags.logtags
@@ -0,0 +1,8 @@
+# See system/logging/logcat/event.logtags for a description of the format of this file.
+
+option java_package com.android.server.contentcapture
+
+53200 cc_connect_state_changed (User|1|5),(component|3),(type|1)
+53201 cc_set_allowlist (User|1|5),(package_count|1),(activity_count|1)
+53202 cc_current_allowlist (User|1|5),(count|1)
+53203 cc_update_options (User|1|5),(count|1)
diff --git a/services/contentcapture/java/com/android/server/contentcapture/RemoteContentCaptureService.java b/services/contentcapture/java/com/android/server/contentcapture/RemoteContentCaptureService.java
index 1efe55a..e22a9d0 100644
--- a/services/contentcapture/java/com/android/server/contentcapture/RemoteContentCaptureService.java
+++ b/services/contentcapture/java/com/android/server/contentcapture/RemoteContentCaptureService.java
@@ -31,6 +31,7 @@
 import android.service.contentcapture.IContentCaptureServiceCallback;
 import android.service.contentcapture.IDataShareCallback;
 import android.service.contentcapture.SnapshotData;
+import android.util.EventLog;
 import android.util.Slog;
 import android.view.contentcapture.ContentCaptureContext;
 import android.view.contentcapture.DataRemovalRequest;
@@ -47,6 +48,7 @@
     private final IBinder mServerCallback;
     private final int mIdleUnbindTimeoutMs;
     private final ContentCapturePerUserService mPerUserService;
+    private final int mUserId;
 
     RemoteContentCaptureService(Context context, String serviceInterface,
             ComponentName serviceComponentName, IContentCaptureServiceCallback callback, int userId,
@@ -61,6 +63,7 @@
         mPerUserService = perUserService;
         mServerCallback = callback.asBinder();
         mIdleUnbindTimeoutMs = idleUnbindTimeoutMs;
+        mUserId = userId;
 
         // Bind right away, which will trigger a onConnected() on service's
         ensureBoundLocked();
@@ -88,6 +91,9 @@
                     writeServiceEvent(
                             FrameworkStatsLog.CONTENT_CAPTURE_SERVICE_EVENTS__EVENT__ON_CONNECTED,
                             mComponentName);
+                    EventLog.writeEvent(EventLogTags.CC_CONNECT_STATE_CHANGED, mUserId,
+                            mComponentName != null ? mComponentName.flattenToShortString() : "",
+                            ContentCapturePerUserService.EVENT_LOG_CONNECT_STATE_CONNECTED);
                 } finally {
                     // Update the system-service state, in case the service reconnected after
                     // dying
@@ -98,6 +104,9 @@
                 writeServiceEvent(
                         FrameworkStatsLog.CONTENT_CAPTURE_SERVICE_EVENTS__EVENT__ON_DISCONNECTED,
                         mComponentName);
+                EventLog.writeEvent(EventLogTags.CC_CONNECT_STATE_CHANGED, mUserId,
+                        mComponentName != null ? mComponentName.flattenToShortString() : "",
+                        ContentCapturePerUserService.EVENT_LOG_CONNECT_STATE_DISCONNECTED);
             }
         } catch (Exception e) {
             Slog.w(mTag, "Exception calling onConnectedStateChanged(" + connected + "): " + e);
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/log/BiometricLogger.java b/services/core/java/com/android/server/biometrics/log/BiometricLogger.java
index ad24cf0..262be08 100644
--- a/services/core/java/com/android/server/biometrics/log/BiometricLogger.java
+++ b/services/core/java/com/android/server/biometrics/log/BiometricLogger.java
@@ -28,8 +28,10 @@
 import android.hardware.biometrics.common.OperationContext;
 import android.hardware.face.FaceManager;
 import android.hardware.fingerprint.FingerprintManager;
+import android.util.Log;
 import android.util.Slog;
 
+import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.FrameworkStatsLog;
 import com.android.server.biometrics.Utils;
@@ -41,6 +43,10 @@
 
     public static final String TAG = "BiometricLogger";
     public static final boolean DEBUG = false;
+    private static final Object sLock = new Object();
+
+    @GuardedBy("sLock")
+    private static int sAlsCounter;
 
     private final int mStatsModality;
     private final int mStatsAction;
@@ -345,13 +351,33 @@
             if (!mLightSensorEnabled) {
                 mLightSensorEnabled = true;
                 mLastAmbientLux = 0;
-                mSensorManager.registerListener(mLightSensorListener, lightSensor,
-                        SensorManager.SENSOR_DELAY_NORMAL);
+                int localAlsCounter;
+                synchronized (sLock) {
+                    localAlsCounter = sAlsCounter++;
+                }
+
+                if (localAlsCounter == 0) {
+                    mSensorManager.registerListener(mLightSensorListener, lightSensor,
+                            SensorManager.SENSOR_DELAY_NORMAL);
+                } else {
+                    Slog.e(TAG, "Ignoring request to subscribe to ALSProbe due to non-zero ALS"
+                            + " counter: " + localAlsCounter);
+                    Slog.e(TAG, Log.getStackTraceString(new Throwable()));
+                }
             }
         } else {
             mLightSensorEnabled = false;
             mLastAmbientLux = 0;
             mSensorManager.unregisterListener(mLightSensorListener);
+            int localAlsCounter;
+            synchronized (sLock) {
+                localAlsCounter = --sAlsCounter;
+            }
+            if (localAlsCounter != 0) {
+                Slog.e(TAG, "Non-zero ALS counter after unsubscribing from ALSProbe: "
+                        + localAlsCounter);
+                Slog.e(TAG, Log.getStackTraceString(new Throwable()));
+            }
         }
     }
 }
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/input/GestureMonitorSpyWindow.java b/services/core/java/com/android/server/input/GestureMonitorSpyWindow.java
index e222c64..d238dae 100644
--- a/services/core/java/com/android/server/input/GestureMonitorSpyWindow.java
+++ b/services/core/java/com/android/server/input/GestureMonitorSpyWindow.java
@@ -27,8 +27,6 @@
 import android.view.SurfaceControl;
 import android.view.WindowManager;
 
-import com.android.server.policy.WindowManagerPolicy;
-
 /**
  * An internal implementation of an {@link InputMonitor} that uses a spy window.
  *
@@ -69,9 +67,7 @@
 
         final SurfaceControl.Transaction t = new SurfaceControl.Transaction();
         t.setInputWindowInfo(mInputSurface, mWindowHandle);
-        // Gesture monitor should be above handwriting event surface, hence setting it to
-        // WindowManagerPolicy.INPUT_DISPLAY_OVERLAY_LAYER + 1
-        t.setLayer(mInputSurface, WindowManagerPolicy.INPUT_DISPLAY_OVERLAY_LAYER + 1);
+        t.setLayer(mInputSurface, Integer.MAX_VALUE);
         t.setPosition(mInputSurface, 0, 0);
         t.setCrop(mInputSurface, null /* crop to parent surface */);
         t.show(mInputSurface);
diff --git a/services/core/java/com/android/server/inputmethod/HandwritingEventReceiverSurface.java b/services/core/java/com/android/server/inputmethod/HandwritingEventReceiverSurface.java
index 5438faa..8180e66 100644
--- a/services/core/java/com/android/server/inputmethod/HandwritingEventReceiverSurface.java
+++ b/services/core/java/com/android/server/inputmethod/HandwritingEventReceiverSurface.java
@@ -27,8 +27,6 @@
 import android.view.SurfaceControl;
 import android.view.WindowManager;
 
-import com.android.server.policy.WindowManagerPolicy;
-
 final class HandwritingEventReceiverSurface {
 
     public static final String TAG = HandwritingEventReceiverSurface.class.getSimpleName();
@@ -38,8 +36,7 @@
     // is above gesture monitors, then edge-back and swipe-up gestures won't work when this surface
     // is intercepting.
     // TODO(b/217538817): Specify the ordering in WM by usage.
-    private static final int HANDWRITING_SURFACE_LAYER =
-            WindowManagerPolicy.INPUT_DISPLAY_OVERLAY_LAYER;
+    private static final int HANDWRITING_SURFACE_LAYER = Integer.MAX_VALUE - 1;
 
     private final InputWindowHandle mWindowHandle;
     private final InputChannel mClientChannel;
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/policy/WindowManagerPolicy.java b/services/core/java/com/android/server/policy/WindowManagerPolicy.java
index 9bc0553..e8a3dcd 100644
--- a/services/core/java/com/android/server/policy/WindowManagerPolicy.java
+++ b/services/core/java/com/android/server/policy/WindowManagerPolicy.java
@@ -155,10 +155,6 @@
     int FINISH_LAYOUT_REDO_ANIM = 0x0008;
     /** Layer for the screen off animation */
     int COLOR_FADE_LAYER = 0x40000001;
-    /** Layer for Input overlays for capturing inputs for gesture detection, etc. */
-    int INPUT_DISPLAY_OVERLAY_LAYER = 0x7f000000;
-    /** Layer for Screen Decoration: The top most visible layer just below input overlay layers */
-    int SCREEN_DECOR_DISPLAY_OVERLAY_LAYER = INPUT_DISPLAY_OVERLAY_LAYER - 1;
 
     /**
      * Register shortcuts for window manager to dispatch.
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/InputManagerCallback.java b/services/core/java/com/android/server/wm/InputManagerCallback.java
index b7ddbd0..33cdd2e 100644
--- a/services/core/java/com/android/server/wm/InputManagerCallback.java
+++ b/services/core/java/com/android/server/wm/InputManagerCallback.java
@@ -265,7 +265,7 @@
                     .setContainerLayer()
                     .setName(name)
                     .setCallsite("createSurfaceForGestureMonitor")
-                    .setParent(dc.getOverlayLayer())
+                    .setParent(dc.getSurfaceControl())
                     .build();
         }
     }
diff --git a/services/core/java/com/android/server/wm/RecentsAnimationController.java b/services/core/java/com/android/server/wm/RecentsAnimationController.java
index 08bf7bc..53f1fe6 100644
--- a/services/core/java/com/android/server/wm/RecentsAnimationController.java
+++ b/services/core/java/com/android/server/wm/RecentsAnimationController.java
@@ -352,10 +352,6 @@
             }
         }
 
-        // TODO(b/166736352): Remove this method without the need to expose to launcher.
-        @Override
-        public void hideCurrentInputMethod() { }
-
         @Override
         public void setDeferCancelUntilNextTransition(boolean defer, boolean screenshot) {
             synchronized (mService.mGlobalLock) {
diff --git a/services/core/java/com/android/server/wm/ScreenRotationAnimation.java b/services/core/java/com/android/server/wm/ScreenRotationAnimation.java
index d09068b..8cad165 100644
--- a/services/core/java/com/android/server/wm/ScreenRotationAnimation.java
+++ b/services/core/java/com/android/server/wm/ScreenRotationAnimation.java
@@ -173,11 +173,6 @@
 
             if (isSizeChanged) {
                 mRoundedCornerOverlay = displayContent.findRoundedCornerOverlays();
-            } else {
-                // Exclude rounded corner overlay from screenshot buffer. Rounded
-                // corner overlay windows are un-rotated during rotation animation
-                // for a seamless transition.
-                builder.setExcludeLayers(displayContent.findRoundedCornerOverlays());
             }
 
             SurfaceControl.ScreenshotHardwareBuffer screenshotBuffer =
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/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index d31dfee..9d6e250 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -8262,7 +8262,7 @@
                         .setContainerLayer()
                         .setName("IME Handwriting Surface")
                         .setCallsite("getHandwritingSurfaceForDisplay")
-                        .setParent(dc.getOverlayLayer())
+                        .setParent(dc.getSurfaceControl())
                         .build();
             }
         }
diff --git a/services/core/java/com/android/server/wm/WindowToken.java b/services/core/java/com/android/server/wm/WindowToken.java
index 437c934..bbb21f8 100644
--- a/services/core/java/com/android/server/wm/WindowToken.java
+++ b/services/core/java/com/android/server/wm/WindowToken.java
@@ -362,7 +362,7 @@
     @Override
     void assignLayer(SurfaceControl.Transaction t, int layer) {
         if (mRoundedCornerOverlay) {
-            super.assignLayer(t, WindowManagerPolicy.SCREEN_DECOR_DISPLAY_OVERLAY_LAYER);
+            super.assignLayer(t, WindowManagerPolicy.COLOR_FADE_LAYER + 1);
         } else {
             super.assignLayer(t, layer);
         }
@@ -372,7 +372,7 @@
     SurfaceControl.Builder makeSurface() {
         final SurfaceControl.Builder builder = super.makeSurface();
         if (mRoundedCornerOverlay) {
-            builder.setParent(getDisplayContent().getOverlayLayer());
+            builder.setParent(null);
         }
         return builder;
     }
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/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;