Merge "Apply navigation bars insets to the fullscreen dialog" into tm-qpr-dev
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/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/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/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..586eab0 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
@@ -57,15 +57,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 +82,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 +168,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 +196,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 +360,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 +394,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)
     //
 
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/sysui/ShellController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellController.java
index 28a30f4..837acec 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,9 @@
     private final ShellExecutor mMainExecutor;
     private final ShellInterfaceImpl mImpl = new ShellInterfaceImpl();
 
-    private final CopyOnWriteArrayList<ConfigurationChangeListener> mListeners =
+    private final CopyOnWriteArrayList<ConfigurationChangeListener> mConfigChangeListeners =
+            new CopyOnWriteArrayList<>();
+    private final CopyOnWriteArrayList<KeyguardChangeListener> mKeyguardChangeListeners =
             new CopyOnWriteArrayList<>();
     private Configuration mLastConfiguration;
 
@@ -68,15 +70,31 @@
      * 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 +120,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 +137,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());
     }
 
     /**
@@ -136,5 +169,19 @@
             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/sysui/ShellInterface.java b/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellInterface.java
index a2d072c..a15ce5d 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
@@ -30,4 +30,16 @@
      * 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/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/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/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/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/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..24fcf15 100644
--- a/packages/SystemUI/src/com/android/systemui/SystemUIInitializer.java
+++ b/packages/SystemUI/src/com/android/systemui/SystemUIInitializer.java
@@ -96,16 +96,12 @@
                     .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 +112,12 @@
                     .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..fd7680f 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java
@@ -41,17 +41,13 @@
 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,9 +96,6 @@
         Builder setTaskViewFactory(Optional<TaskViewFactory> t);
 
         @BindsInstance
-        Builder setHideDisplayCutout(Optional<HideDisplayCutout> h);
-
-        @BindsInstance
         Builder setShellCommandHandler(Optional<ShellCommandHandler> shellDump);
 
         @BindsInstance
@@ -115,18 +108,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..e4c0325 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/WMComponent.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/WMComponent.java
@@ -29,20 +29,16 @@
 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;
@@ -104,9 +100,6 @@
     Optional<Bubbles> getBubbles();
 
     @WMSingleton
-    Optional<HideDisplayCutout> getHideDisplayCutout();
-
-    @WMSingleton
     Optional<TaskViewFactory> getTaskViewFactory();
 
     @WMSingleton
@@ -119,17 +112,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/Flags.java b/packages/SystemUI/src/com/android/systemui/flags/Flags.java
index 245ea21..46173b8 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.java
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.java
@@ -142,6 +142,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/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/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/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/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/fragment/CollapsedStatusBarFragment.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java
index 0e7da81..39bf92a 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
@@ -102,7 +102,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;
@@ -232,16 +232,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);
     }
@@ -370,10 +370,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 +474,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);
         }
     }
 
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..83b0022 100644
--- a/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
+++ b/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
@@ -55,9 +55,6 @@
 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 +107,7 @@
     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 +121,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 +130,7 @@
             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 +152,9 @@
         mPipOptional = pipOptional;
         mSplitScreenOptional = splitScreenOptional;
         mOneHandedOptional = oneHandedOptional;
-        mHideDisplayCutoutOptional = hideDisplayCutoutOptional;
         mWakefulnessLifecycle = wakefulnessLifecycle;
         mProtoTracer = protoTracer;
         mShellCommandHandler = shellCommandHandler;
-        mCompatUIOptional = sizeCompatUIOptional;
-        mDragAndDropOptional = dragAndDropOptional;
         mUserInfoController = userInfoController;
         mSysUiMainExecutor = sysUiMainExecutor;
     }
@@ -185,6 +170,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 +193,6 @@
         mPipOptional.ifPresent(this::initPip);
         mSplitScreenOptional.ifPresent(this::initSplitScreen);
         mOneHandedOptional.ifPresent(this::initOneHanded);
-        mCompatUIOptional.ifPresent(this::initCompatUi);
     }
 
     @VisibleForTesting
@@ -204,20 +204,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 +216,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 +261,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 +313,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) {
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/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/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/fragment/CollapsedStatusBarFragmentTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java
index 3f14494..681e998 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
@@ -124,7 +124,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 +134,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 +234,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 +246,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 +257,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 +273,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());
     }
 
@@ -419,7 +419,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/WMShellTest.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/WMShellTest.java
index 2b37b9e..72ade26 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/WMShellTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/WMShellTest.java
@@ -24,12 +24,10 @@
 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;
@@ -37,9 +35,6 @@
 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,31 +66,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,
+                Optional.of(mSplitScreen), Optional.of(mOneHanded),
+                Optional.of(mShellCommandHandler), mCommandQueue, mConfigurationController,
+                mKeyguardStateController, mKeyguardUpdateMonitor, mScreenLifecycle, mSysUiState,
                 mProtoTracer, mWakefulnessLifecycle, mUserInfoController, mSysUiMainExecutor);
     }
 
@@ -107,27 +96,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/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index 0040ea9..90a47a4 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -10373,6 +10373,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/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/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/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/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/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;
     }