Merge "Reintroduce WCG drawing if supported" into tm-qpr-dev
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index 34c91c3..8e09939 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -12150,6 +12150,15 @@
      * Attempts by the admin to grant these permissions, when the admin is restricted from doing
      * so, will be silently ignored (no exception will be thrown).
      *
+     * Control over the following permissions are restricted for managed profile owners:
+     * <ul>
+     *  <li>Manifest.permission.READ_SMS</li>
+     * </ul>
+     * <p>
+     * A managed profile owner may not grant these permissions (i.e. call this method with any of
+     * the permissions listed above and {@code grantState} of
+     * {@code #PERMISSION_GRANT_STATE_GRANTED}), but may deny them.
+     *
      * @param admin Which profile or device owner this request is associated with.
      * @param packageName The application to grant or revoke a permission to.
      * @param permission The permission to grant or revoke.
diff --git a/core/java/android/os/BatteryStats.java b/core/java/android/os/BatteryStats.java
index 06c35b5..3d5c34c 100644
--- a/core/java/android/os/BatteryStats.java
+++ b/core/java/android/os/BatteryStats.java
@@ -962,7 +962,7 @@
          * also be bumped.
          */
         static final String[] USER_ACTIVITY_TYPES = {
-            "other", "button", "touch", "accessibility", "attention"
+            "other", "button", "touch", "accessibility", "attention", "faceDown", "deviceState"
         };
 
         public static final int NUM_USER_ACTIVITY_TYPES = USER_ACTIVITY_TYPES.length;
diff --git a/core/java/android/os/PowerManager.java b/core/java/android/os/PowerManager.java
index 13ca2c3..a46868e 100644
--- a/core/java/android/os/PowerManager.java
+++ b/core/java/android/os/PowerManager.java
@@ -345,6 +345,39 @@
     public static final int USER_ACTIVITY_EVENT_DEVICE_STATE = 6;
 
     /**
+     * @hide
+     */
+    @IntDef(prefix = { "USER_ACTIVITY_EVENT_" }, value = {
+            USER_ACTIVITY_EVENT_OTHER,
+            USER_ACTIVITY_EVENT_BUTTON,
+            USER_ACTIVITY_EVENT_TOUCH,
+            USER_ACTIVITY_EVENT_ACCESSIBILITY,
+            USER_ACTIVITY_EVENT_ATTENTION,
+            USER_ACTIVITY_EVENT_FACE_DOWN,
+            USER_ACTIVITY_EVENT_DEVICE_STATE,
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface UserActivityEvent{}
+
+    /**
+     *
+     * Convert the user activity event to a string for debugging purposes.
+     * @hide
+     */
+    public static String userActivityEventToString(@UserActivityEvent int userActivityEvent) {
+        switch (userActivityEvent) {
+            case USER_ACTIVITY_EVENT_OTHER: return "other";
+            case USER_ACTIVITY_EVENT_BUTTON: return "button";
+            case USER_ACTIVITY_EVENT_TOUCH: return "touch";
+            case USER_ACTIVITY_EVENT_ACCESSIBILITY: return "accessibility";
+            case USER_ACTIVITY_EVENT_ATTENTION: return "attention";
+            case USER_ACTIVITY_EVENT_FACE_DOWN: return "faceDown";
+            case USER_ACTIVITY_EVENT_DEVICE_STATE: return "deviceState";
+            default: return Integer.toString(userActivityEvent);
+        }
+    }
+
+    /**
      * User activity flag: If already dimmed, extend the dim timeout
      * but do not brighten.  This flag is useful for keeping the screen on
      * a little longer without causing a visible change such as when
diff --git a/core/java/com/android/internal/os/BatteryStatsImpl.java b/core/java/com/android/internal/os/BatteryStatsImpl.java
index ec2bc7c..8b96597 100644
--- a/core/java/com/android/internal/os/BatteryStatsImpl.java
+++ b/core/java/com/android/internal/os/BatteryStatsImpl.java
@@ -167,7 +167,7 @@
     private static final int MAGIC = 0xBA757475; // 'BATSTATS'
 
     // Current on-disk Parcel version
-    static final int VERSION = 208;
+    static final int VERSION = 210;
 
     // The maximum number of names wakelocks we will keep track of
     // per uid; once the limit is reached, we batch the remaining wakelocks
@@ -6145,12 +6145,13 @@
 
     @UnsupportedAppUsage
     @GuardedBy("this")
-    public void noteUserActivityLocked(int uid, int event) {
+    public void noteUserActivityLocked(int uid, @PowerManager.UserActivityEvent int event) {
         noteUserActivityLocked(uid, event, mClock.elapsedRealtime(), mClock.uptimeMillis());
     }
 
     @GuardedBy("this")
-    public void noteUserActivityLocked(int uid, int event, long elapsedRealtimeMs, long uptimeMs) {
+    public void noteUserActivityLocked(int uid, @PowerManager.UserActivityEvent int event,
+            long elapsedRealtimeMs, long uptimeMs) {
         if (mOnBatteryInternal) {
             uid = mapUid(uid);
             getUidStatsLocked(uid, elapsedRealtimeMs, uptimeMs).noteUserActivityLocked(event);
@@ -9962,14 +9963,14 @@
         }
 
         @Override
-        public void noteUserActivityLocked(int type) {
+        public void noteUserActivityLocked(@PowerManager.UserActivityEvent int event) {
             if (mUserActivityCounters == null) {
                 initUserActivityLocked();
             }
-            if (type >= 0 && type < NUM_USER_ACTIVITY_TYPES) {
-                mUserActivityCounters[type].stepAtomic();
+            if (event >= 0 && event < NUM_USER_ACTIVITY_TYPES) {
+                mUserActivityCounters[event].stepAtomic();
             } else {
-                Slog.w(TAG, "Unknown user activity type " + type + " was specified.",
+                Slog.w(TAG, "Unknown user activity type " + event + " was specified.",
                         new Throwable());
             }
         }
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/layout/WindowLayoutComponentImpl.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/layout/WindowLayoutComponentImpl.java
index c76f568..0fb6ff8 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/layout/WindowLayoutComponentImpl.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/layout/WindowLayoutComponentImpl.java
@@ -103,14 +103,23 @@
     /**
      * Similar to {@link #addWindowLayoutInfoListener(Activity, Consumer)}, but takes a UI Context
      * as a parameter.
+     *
+     * Jetpack {@link androidx.window.layout.ExtensionWindowLayoutInfoBackend} makes sure all
+     * consumers related to the same {@link Context} gets updated {@link WindowLayoutInfo}
+     * together. However only the first registered consumer of a {@link Context} will actually
+     * invoke {@link #addWindowLayoutInfoListener(Context, Consumer)}.
+     * Here we enforce that {@link #addWindowLayoutInfoListener(Context, Consumer)} can only be
+     * called once for each {@link Context}.
      */
-    // TODO(b/204073440): Add @Override to hook the API in WM extensions library.
+    @Override
     public void addWindowLayoutInfoListener(@NonNull @UiContext Context context,
             @NonNull Consumer<WindowLayoutInfo> consumer) {
         if (mWindowLayoutChangeListeners.containsKey(context)
+                // In theory this method can be called on the same consumer with different context.
                 || mWindowLayoutChangeListeners.containsValue(consumer)) {
-            // Early return if the listener or consumer has been registered.
-            return;
+            throw new IllegalArgumentException(
+                    "Context or Consumer has already been registered for WindowLayoutInfo"
+                            + " callback.");
         }
         if (!context.isUiContext()) {
             throw new IllegalArgumentException("Context must be a UI Context, which should be"
diff --git a/libs/WindowManager/Jetpack/window-extensions-release.aar b/libs/WindowManager/Jetpack/window-extensions-release.aar
index 2c766d8..b0b95f9 100644
--- a/libs/WindowManager/Jetpack/window-extensions-release.aar
+++ b/libs/WindowManager/Jetpack/window-extensions-release.aar
Binary files differ
diff --git a/libs/WindowManager/Shell/Android.bp b/libs/WindowManager/Shell/Android.bp
index 9ee52cc..82573b2 100644
--- a/libs/WindowManager/Shell/Android.bp
+++ b/libs/WindowManager/Shell/Android.bp
@@ -44,6 +44,7 @@
     srcs: [
         "src/com/android/wm/shell/util/**/*.java",
         "src/com/android/wm/shell/common/split/SplitScreenConstants.java",
+        "src/com/android/wm/shell/sysui/ShellSharedConstants.java",
     ],
     path: "src",
 }
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 86f9d5b..8cbe44b 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,13 +29,6 @@
 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}.
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java
index 33ecdd8..938189f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java
@@ -21,6 +21,7 @@
 
 import static com.android.wm.shell.common.ExecutorUtils.executeRemoteCallWithTaskPermission;
 import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_BACK_PREVIEW;
+import static com.android.wm.shell.sysui.ShellSharedConstants.KEY_EXTRA_SHELL_BACK_ANIMATION;
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -57,10 +58,12 @@
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.protolog.common.ProtoLog;
+import com.android.wm.shell.common.ExternalInterfaceBinder;
 import com.android.wm.shell.common.RemoteCallable;
 import com.android.wm.shell.common.ShellExecutor;
 import com.android.wm.shell.common.annotations.ShellBackgroundThread;
 import com.android.wm.shell.common.annotations.ShellMainThread;
+import com.android.wm.shell.sysui.ShellController;
 import com.android.wm.shell.sysui.ShellInit;
 
 import java.util.concurrent.atomic.AtomicBoolean;
@@ -105,6 +108,7 @@
     private final IActivityTaskManager mActivityTaskManager;
     private final Context mContext;
     private final ContentResolver mContentResolver;
+    private final ShellController mShellController;
     private final ShellExecutor mShellExecutor;
     private final Handler mBgHandler;
     @Nullable
@@ -231,21 +235,25 @@
 
     public BackAnimationController(
             @NonNull ShellInit shellInit,
+            @NonNull ShellController shellController,
             @NonNull @ShellMainThread ShellExecutor shellExecutor,
             @NonNull @ShellBackgroundThread Handler backgroundHandler,
             Context context) {
-        this(shellInit, shellExecutor, backgroundHandler, new SurfaceControl.Transaction(),
-                ActivityTaskManager.getService(), context, context.getContentResolver());
+        this(shellInit, shellController, shellExecutor, backgroundHandler,
+                new SurfaceControl.Transaction(), ActivityTaskManager.getService(),
+                context, context.getContentResolver());
     }
 
     @VisibleForTesting
     BackAnimationController(
             @NonNull ShellInit shellInit,
+            @NonNull ShellController shellController,
             @NonNull @ShellMainThread ShellExecutor shellExecutor,
             @NonNull @ShellBackgroundThread Handler bgHandler,
             @NonNull SurfaceControl.Transaction transaction,
             @NonNull IActivityTaskManager activityTaskManager,
             Context context, ContentResolver contentResolver) {
+        mShellController = shellController;
         mShellExecutor = shellExecutor;
         mTransaction = transaction;
         mActivityTaskManager = activityTaskManager;
@@ -257,6 +265,8 @@
 
     private void onInit() {
         setupAnimationDeveloperSettingsObserver(mContentResolver, mBgHandler);
+        mShellController.addExternalInterface(KEY_EXTRA_SHELL_BACK_ANIMATION,
+                this::createExternalInterface, this);
     }
 
     private void setupAnimationDeveloperSettingsObserver(
@@ -289,7 +299,11 @@
         return mBackAnimation;
     }
 
-    private final BackAnimation mBackAnimation = new BackAnimationImpl();
+    private ExternalInterfaceBinder createExternalInterface() {
+        return new IBackAnimationImpl(this);
+    }
+
+    private final BackAnimationImpl mBackAnimation = new BackAnimationImpl();
 
     @Override
     public Context getContext() {
@@ -302,17 +316,6 @@
     }
 
     private class BackAnimationImpl implements BackAnimation {
-        private IBackAnimationImpl mBackAnimation;
-
-        @Override
-        public IBackAnimation createExternalInterface() {
-            if (mBackAnimation != null) {
-                mBackAnimation.invalidate();
-            }
-            mBackAnimation = new IBackAnimationImpl(BackAnimationController.this);
-            return mBackAnimation;
-        }
-
         @Override
         public void onBackMotion(
                 float touchX, float touchY, int keyAction, @BackEvent.SwipeEdge int swipeEdge) {
@@ -331,7 +334,8 @@
         }
     }
 
-    private static class IBackAnimationImpl extends IBackAnimation.Stub {
+    private static class IBackAnimationImpl extends IBackAnimation.Stub
+            implements ExternalInterfaceBinder {
         private BackAnimationController mController;
 
         IBackAnimationImpl(BackAnimationController controller) {
@@ -356,7 +360,8 @@
                     (controller) -> controller.onBackToLauncherAnimationFinished());
         }
 
-        void invalidate() {
+        @Override
+        public void invalidate() {
             mController = null;
         }
     }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/ExternalInterfaceBinder.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/ExternalInterfaceBinder.java
new file mode 100644
index 0000000..aa5b0cb
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/ExternalInterfaceBinder.java
@@ -0,0 +1,34 @@
+/*
+ * 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.common;
+
+import android.os.IBinder;
+
+/**
+ * An interface for binders which can be registered to be sent to other processes.
+ */
+public interface ExternalInterfaceBinder {
+    /**
+     * Invalidates this binder (detaches it from the controller it would call).
+     */
+    void invalidate();
+
+    /**
+     * Returns the IBinder to send.
+     */
+    IBinder asBinder();
+}
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 7d0c4bd..28a1959 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
@@ -261,13 +261,14 @@
     static Optional<BackAnimationController> provideBackAnimationController(
             Context context,
             ShellInit shellInit,
+            ShellController shellController,
             @ShellMainThread ShellExecutor shellExecutor,
             @ShellBackgroundThread Handler backgroundHandler
     ) {
         if (BackAnimationController.IS_ENABLED) {
             return Optional.of(
-                    new BackAnimationController(shellInit, shellExecutor, backgroundHandler,
-                            context));
+                    new BackAnimationController(shellInit, shellController, shellExecutor,
+                            backgroundHandler, context));
         }
         return Optional.empty();
     }
@@ -471,6 +472,7 @@
     static Optional<RecentTasksController> provideRecentTasksController(
             Context context,
             ShellInit shellInit,
+            ShellController shellController,
             ShellCommandHandler shellCommandHandler,
             TaskStackListenerImpl taskStackListener,
             ActivityTaskManager activityTaskManager,
@@ -478,9 +480,9 @@
             @ShellMainThread ShellExecutor mainExecutor
     ) {
         return Optional.ofNullable(
-                RecentTasksController.create(context, shellInit, shellCommandHandler,
-                        taskStackListener, activityTaskManager, desktopModeTaskRepository,
-                        mainExecutor));
+                RecentTasksController.create(context, shellInit, shellController,
+                        shellCommandHandler, taskStackListener, activityTaskManager,
+                        desktopModeTaskRepository, mainExecutor));
     }
 
     //
@@ -497,14 +499,15 @@
     @Provides
     static Transitions provideTransitions(Context context,
             ShellInit shellInit,
+            ShellController shellController,
             ShellTaskOrganizer organizer,
             TransactionPool pool,
             DisplayController displayController,
             @ShellMainThread ShellExecutor mainExecutor,
             @ShellMainThread Handler mainHandler,
             @ShellAnimationThread ShellExecutor animExecutor) {
-        return new Transitions(context, shellInit, organizer, pool, displayController, mainExecutor,
-                mainHandler, animExecutor);
+        return new Transitions(context, shellInit, shellController, organizer, pool,
+                displayController, mainExecutor, mainHandler, animExecutor);
     }
 
     @WMSingleton
@@ -621,13 +624,15 @@
 
     @WMSingleton
     @Provides
-    static StartingWindowController provideStartingWindowController(Context context,
+    static StartingWindowController provideStartingWindowController(
+            Context context,
             ShellInit shellInit,
+            ShellController shellController,
             ShellTaskOrganizer shellTaskOrganizer,
             @ShellSplashscreenThread ShellExecutor splashScreenExecutor,
             StartingWindowTypeAlgorithm startingWindowTypeAlgorithm, IconProvider iconProvider,
             TransactionPool pool) {
-        return new StartingWindowController(context, shellInit, shellTaskOrganizer,
+        return new StartingWindowController(context, shellInit, shellController, shellTaskOrganizer,
                 splashScreenExecutor, startingWindowTypeAlgorithm, iconProvider, pool);
     }
 
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 461d7dc..1ec98d3 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
@@ -598,7 +598,9 @@
     @WMSingleton
     @Provides
     @DynamicOverride
-    static DesktopModeController provideDesktopModeController(Context context, ShellInit shellInit,
+    static DesktopModeController provideDesktopModeController(Context context,
+            ShellInit shellInit,
+            ShellController shellController,
             ShellTaskOrganizer shellTaskOrganizer,
             RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer,
             Transitions transitions,
@@ -606,7 +608,7 @@
             @ShellMainThread Handler mainHandler,
             @ShellMainThread ShellExecutor mainExecutor
     ) {
-        return new DesktopModeController(context, shellInit, shellTaskOrganizer,
+        return new DesktopModeController(context, shellInit, shellController, shellTaskOrganizer,
                 rootTaskDisplayAreaOrganizer, transitions, desktopModeTaskRepository, mainHandler,
                 mainExecutor);
     }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopMode.java b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopMode.java
index ff3be38..44a467f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopMode.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopMode.java
@@ -23,9 +23,4 @@
  */
 @ExternalThread
 public interface DesktopMode {
-
-    /** Returns a binder that can be passed to an external process to manipulate DesktopMode. */
-    default IDesktopMode createExternalInterface() {
-        return null;
-    }
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeController.java
index dc7ed15..b96facf 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeController.java
@@ -23,6 +23,7 @@
 
 import static com.android.wm.shell.common.ExecutorUtils.executeRemoteCallWithTaskPermission;
 import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE;
+import static com.android.wm.shell.sysui.ShellSharedConstants.KEY_EXTRA_SHELL_DESKTOP_MODE;
 
 import android.app.ActivityManager.RunningTaskInfo;
 import android.app.WindowConfiguration;
@@ -48,10 +49,12 @@
 import com.android.internal.protolog.common.ProtoLog;
 import com.android.wm.shell.RootTaskDisplayAreaOrganizer;
 import com.android.wm.shell.ShellTaskOrganizer;
+import com.android.wm.shell.common.ExternalInterfaceBinder;
 import com.android.wm.shell.common.RemoteCallable;
 import com.android.wm.shell.common.ShellExecutor;
 import com.android.wm.shell.common.annotations.ExternalThread;
 import com.android.wm.shell.common.annotations.ShellMainThread;
+import com.android.wm.shell.sysui.ShellController;
 import com.android.wm.shell.sysui.ShellInit;
 import com.android.wm.shell.transition.Transitions;
 
@@ -65,15 +68,18 @@
         Transitions.TransitionHandler {
 
     private final Context mContext;
+    private final ShellController mShellController;
     private final ShellTaskOrganizer mShellTaskOrganizer;
     private final RootTaskDisplayAreaOrganizer mRootTaskDisplayAreaOrganizer;
     private final Transitions mTransitions;
     private final DesktopModeTaskRepository mDesktopModeTaskRepository;
     private final ShellExecutor mMainExecutor;
-    private final DesktopMode mDesktopModeImpl = new DesktopModeImpl();
+    private final DesktopModeImpl mDesktopModeImpl = new DesktopModeImpl();
     private final SettingsObserver mSettingsObserver;
 
-    public DesktopModeController(Context context, ShellInit shellInit,
+    public DesktopModeController(Context context,
+            ShellInit shellInit,
+            ShellController shellController,
             ShellTaskOrganizer shellTaskOrganizer,
             RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer,
             Transitions transitions,
@@ -81,6 +87,7 @@
             @ShellMainThread Handler mainHandler,
             @ShellMainThread ShellExecutor mainExecutor) {
         mContext = context;
+        mShellController = shellController;
         mShellTaskOrganizer = shellTaskOrganizer;
         mRootTaskDisplayAreaOrganizer = rootTaskDisplayAreaOrganizer;
         mTransitions = transitions;
@@ -92,6 +99,8 @@
 
     private void onInit() {
         ProtoLog.d(WM_SHELL_DESKTOP_MODE, "Initialize DesktopModeController");
+        mShellController.addExternalInterface(KEY_EXTRA_SHELL_DESKTOP_MODE,
+                this::createExternalInterface, this);
         mSettingsObserver.observe();
         if (DesktopModeStatus.isActive(mContext)) {
             updateDesktopModeActive(true);
@@ -116,6 +125,13 @@
         return mDesktopModeImpl;
     }
 
+    /**
+     * Creates a new instance of the external interface to pass to another process.
+     */
+    private ExternalInterfaceBinder createExternalInterface() {
+        return new IDesktopModeImpl(this);
+    }
+
     @VisibleForTesting
     void updateDesktopModeActive(boolean active) {
         ProtoLog.d(WM_SHELL_DESKTOP_MODE, "updateDesktopModeActive: active=%s", active);
@@ -277,24 +293,15 @@
      */
     @ExternalThread
     private final class DesktopModeImpl implements DesktopMode {
-
-        private IDesktopModeImpl mIDesktopMode;
-
-        @Override
-        public IDesktopMode createExternalInterface() {
-            if (mIDesktopMode != null) {
-                mIDesktopMode.invalidate();
-            }
-            mIDesktopMode = new IDesktopModeImpl(DesktopModeController.this);
-            return mIDesktopMode;
-        }
+        // Do nothing
     }
 
     /**
      * The interface for calls from outside the host process.
      */
     @BinderThread
-    private static class IDesktopModeImpl extends IDesktopMode.Stub {
+    private static class IDesktopModeImpl extends IDesktopMode.Stub
+            implements ExternalInterfaceBinder {
 
         private DesktopModeController mController;
 
@@ -305,7 +312,8 @@
         /**
          * Invalidates this instance, preventing future calls from updating the controller.
          */
-        void invalidate() {
+        @Override
+        public void invalidate() {
             mController = null;
         }
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/docs/changes.md b/libs/WindowManager/Shell/src/com/android/wm/shell/docs/changes.md
index 2aa933d..fbf326e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/docs/changes.md
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/docs/changes.md
@@ -29,19 +29,37 @@
 ### SysUI accessible components
 In addition to doing the above, you will also need to provide an interface for calling to SysUI
 from the Shell and vice versa.  The current pattern is to have a parallel `Optional<Component name>`
-interface that the `<Component name>Controller` implements and handles on the main Shell thread.
+interface that the `<Component name>Controller` implements and handles on the main Shell thread
+(see [SysUI/Shell threading](threading.md)).
 
 In addition, because components accessible to SysUI injection are explicitly listed, you'll have to
 add an appropriate method in `WMComponent` to get the interface and update the `Builder` in
 `SysUIComponent` to take the interface so it can be injected in SysUI code.  The binding between
 the two is done in `SystemUIFactory#init()` which will need to be updated as well.
 
+Specifically, to support calling into a controller from an external process (like Launcher):
+- Create an implementation of the external interface within the controller
+- Have all incoming calls post to the main shell thread (inject @ShellMainThread Executor into the
+  controller if needed)
+- Note that callbacks into SysUI should take an associated executor to call back on
+
 ### Launcher accessible components
 Because Launcher is not a part of SystemUI and is a separate process, exposing controllers to
 Launcher requires a new AIDL interface to be created and implemented by the controller.  The
 implementation of the stub interface in the controller otherwise behaves similar to the interface
 to SysUI where it posts the work to the main Shell thread.
 
+Specifically, to support calling into a controller from an external process (like Launcher):
+- Create an implementation of the interface binder's `Stub` class within the controller, have it
+  extend `ExternalInterfaceBinder` and implement `invalidate()` to ensure it doesn't hold long
+  references to the outer controller
+- Make the controller implement `RemoteCallable<T>`, and have all incoming calls use one of
+  the `ExecutorUtils.executeRemoteCallWithTaskPermission()` calls to verify the caller's identity
+  and ensure the call happens on the main shell thread and not the binder thread
+- Inject `ShellController` and add the instance of the implementation as external interface
+- In Launcher, update `TouchInteractionService` to pass the interface to `SystemUIProxy`, and then
+  call the SystemUIProxy method as needed in that code
+
 ### Component initialization
 To initialize the component:
 - On the Shell side, you potentially need to do two things to initialize the component:
@@ -64,8 +82,9 @@
 
 ### General Do's & Dont's
 Do:
-- Do add unit tests for all new components
-- Do keep controllers simple and break them down as needed
+- Add unit tests for all new components
+- Keep controllers simple and break them down as needed
+- Any SysUI callbacks should also take an associated executor to run the callback on
 
 Don't:
 - **Don't** do initialization in the constructor, only do initialization in the init callbacks.
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/floating/FloatingTasks.java b/libs/WindowManager/Shell/src/com/android/wm/shell/floating/FloatingTasks.java
index 9356660..f86d467 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/floating/FloatingTasks.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/floating/FloatingTasks.java
@@ -33,9 +33,4 @@
      * - If there is a floating task for this intent, and it's not stashed, this stashes it.
      */
     void showOrSetStashed(Intent intent);
-
-    /** Returns a binder that can be passed to an external process to manipulate FloatingTasks. */
-    default IFloatingTasks createExternalInterface() {
-        return null;
-    }
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/floating/FloatingTasksController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/floating/FloatingTasksController.java
index 6755299..b3c09d3 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/floating/FloatingTasksController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/floating/FloatingTasksController.java
@@ -21,6 +21,7 @@
 
 import static com.android.wm.shell.common.ExecutorUtils.executeRemoteCallWithTaskPermission;
 import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_FLOATING_APPS;
+import static com.android.wm.shell.sysui.ShellSharedConstants.KEY_EXTRA_SHELL_FLOATING_TASKS;
 
 import android.annotation.Nullable;
 import android.content.Context;
@@ -40,6 +41,7 @@
 import com.android.wm.shell.ShellTaskOrganizer;
 import com.android.wm.shell.TaskViewTransitions;
 import com.android.wm.shell.bubbles.BubbleController;
+import com.android.wm.shell.common.ExternalInterfaceBinder;
 import com.android.wm.shell.common.RemoteCallable;
 import com.android.wm.shell.common.ShellExecutor;
 import com.android.wm.shell.common.SyncTransactionQueue;
@@ -136,11 +138,13 @@
         if (isFloatingTasksEnabled()) {
             shellInit.addInitCallback(this::onInit, this);
         }
-        mShellCommandHandler.addDumpCallback(this::dump, this);
     }
 
     protected void onInit() {
         mShellController.addConfigurationChangeListener(this);
+        mShellController.addExternalInterface(KEY_EXTRA_SHELL_FLOATING_TASKS,
+                this::createExternalInterface, this);
+        mShellCommandHandler.addDumpCallback(this::dump, this);
     }
 
     /** Only used for testing. */
@@ -168,6 +172,10 @@
         return FLOATING_TASKS_ENABLED || mFloatingTasksEnabledForTests;
     }
 
+    private ExternalInterfaceBinder createExternalInterface() {
+        return new IFloatingTasksImpl(this);
+    }
+
     @Override
     public void onThemeChanged() {
         if (mIsFloatingLayerAdded) {
@@ -412,28 +420,18 @@
      */
     @ExternalThread
     private class FloatingTaskImpl implements FloatingTasks {
-        private IFloatingTasksImpl mIFloatingTasks;
-
         @Override
         public void showOrSetStashed(Intent intent) {
             mMainExecutor.execute(() -> FloatingTasksController.this.showOrSetStashed(intent));
         }
-
-        @Override
-        public IFloatingTasks createExternalInterface() {
-            if (mIFloatingTasks != null) {
-                mIFloatingTasks.invalidate();
-            }
-            mIFloatingTasks = new IFloatingTasksImpl(FloatingTasksController.this);
-            return mIFloatingTasks;
-        }
     }
 
     /**
      * The interface for calls from outside the host process.
      */
     @BinderThread
-    private static class IFloatingTasksImpl extends IFloatingTasks.Stub {
+    private static class IFloatingTasksImpl extends IFloatingTasks.Stub
+            implements ExternalInterfaceBinder {
         private FloatingTasksController mController;
 
         IFloatingTasksImpl(FloatingTasksController controller) {
@@ -443,7 +441,8 @@
         /**
          * Invalidates this instance, preventing future calls from updating the controller.
          */
-        void invalidate() {
+        @Override
+        public void invalidate() {
             mController = null;
         }
 
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 7129165..2ee3348 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
@@ -30,13 +30,6 @@
             OneHandedController.SUPPORT_ONE_HANDED_MODE, false);
 
     /**
-     * Returns a binder that can be passed to an external process to manipulate OneHanded.
-     */
-    default IOneHanded createExternalInterface() {
-        return null;
-    }
-
-    /**
      * Enters one handed mode.
      */
     void startOneHanded();
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 e0c4fe8..679d4ca 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
@@ -24,6 +24,7 @@
 import static com.android.wm.shell.onehanded.OneHandedState.STATE_ENTERING;
 import static com.android.wm.shell.onehanded.OneHandedState.STATE_EXITING;
 import static com.android.wm.shell.onehanded.OneHandedState.STATE_NONE;
+import static com.android.wm.shell.sysui.ShellSharedConstants.KEY_EXTRA_SHELL_ONE_HANDED;
 
 import android.annotation.BinderThread;
 import android.content.ComponentName;
@@ -49,6 +50,7 @@
 import com.android.wm.shell.common.DisplayChangeController;
 import com.android.wm.shell.common.DisplayController;
 import com.android.wm.shell.common.DisplayLayout;
+import com.android.wm.shell.common.ExternalInterfaceBinder;
 import com.android.wm.shell.common.RemoteCallable;
 import com.android.wm.shell.common.ShellExecutor;
 import com.android.wm.shell.common.TaskStackListenerCallback;
@@ -296,12 +298,18 @@
         mShellController.addConfigurationChangeListener(this);
         mShellController.addKeyguardChangeListener(this);
         mShellController.addUserChangeListener(this);
+        mShellController.addExternalInterface(KEY_EXTRA_SHELL_ONE_HANDED,
+                this::createExternalInterface, this);
     }
 
     public OneHanded asOneHanded() {
         return mImpl;
     }
 
+    private ExternalInterfaceBinder createExternalInterface() {
+        return new IOneHandedImpl(this);
+    }
+
     @Override
     public Context getContext() {
         return mContext;
@@ -709,17 +717,6 @@
      */
     @ExternalThread
     private class OneHandedImpl implements OneHanded {
-        private IOneHandedImpl mIOneHanded;
-
-        @Override
-        public IOneHanded createExternalInterface() {
-            if (mIOneHanded != null) {
-                mIOneHanded.invalidate();
-            }
-            mIOneHanded = new IOneHandedImpl(OneHandedController.this);
-            return mIOneHanded;
-        }
-
         @Override
         public void startOneHanded() {
             mMainExecutor.execute(() -> {
@@ -767,7 +764,7 @@
      * The interface for calls from outside the host process.
      */
     @BinderThread
-    private static class IOneHandedImpl extends IOneHanded.Stub {
+    private static class IOneHandedImpl extends IOneHanded.Stub implements ExternalInterfaceBinder {
         private OneHandedController mController;
 
         IOneHandedImpl(OneHandedController controller) {
@@ -777,7 +774,8 @@
         /**
          * Invalidates this instance, preventing future calls from updating the controller.
          */
-        void invalidate() {
+        @Override
+        public void invalidate() {
             mController = null;
         }
 
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 72b9dd3..f34d2a8 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
@@ -27,14 +27,6 @@
  */
 @ExternalThread
 public interface Pip {
-
-    /**
-     * Returns a binder that can be passed to an external process to manipulate PIP.
-     */
-    default IPip createExternalInterface() {
-        return null;
-    }
-
     /**
      * Expand PIP, it's possible that specific request to activate the window via Alt-tab.
      */
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 3345b1b..a918559 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
@@ -33,6 +33,7 @@
 import static com.android.wm.shell.pip.PipAnimationController.TRANSITION_DIRECTION_TO_PIP;
 import static com.android.wm.shell.pip.PipAnimationController.TRANSITION_DIRECTION_USER_RESIZE;
 import static com.android.wm.shell.pip.PipAnimationController.isOutPipDirection;
+import static com.android.wm.shell.sysui.ShellSharedConstants.KEY_EXTRA_SHELL_PIP;
 
 import android.app.ActivityManager;
 import android.app.ActivityTaskManager;
@@ -68,6 +69,7 @@
 import com.android.wm.shell.common.DisplayController;
 import com.android.wm.shell.common.DisplayInsetsController;
 import com.android.wm.shell.common.DisplayLayout;
+import com.android.wm.shell.common.ExternalInterfaceBinder;
 import com.android.wm.shell.common.RemoteCallable;
 import com.android.wm.shell.common.ShellExecutor;
 import com.android.wm.shell.common.SingleInstanceRemoteListener;
@@ -632,6 +634,12 @@
         mShellController.addConfigurationChangeListener(this);
         mShellController.addKeyguardChangeListener(this);
         mShellController.addUserChangeListener(this);
+        mShellController.addExternalInterface(KEY_EXTRA_SHELL_PIP,
+                this::createExternalInterface, this);
+    }
+
+    private ExternalInterfaceBinder createExternalInterface() {
+        return new IPipImpl(this);
     }
 
     @Override
@@ -1040,17 +1048,6 @@
      * The interface for calls from outside the Shell, within the host process.
      */
     private class PipImpl implements Pip {
-        private IPipImpl mIPip;
-
-        @Override
-        public IPip createExternalInterface() {
-            if (mIPip != null) {
-                mIPip.invalidate();
-            }
-            mIPip = new IPipImpl(PipController.this);
-            return mIPip;
-        }
-
         @Override
         public void expandPip() {
             mMainExecutor.execute(() -> {
@@ -1098,7 +1095,7 @@
      * The interface for calls from outside the host process.
      */
     @BinderThread
-    private static class IPipImpl extends IPip.Stub {
+    private static class IPipImpl extends IPip.Stub implements ExternalInterfaceBinder {
         private PipController mController;
         private final SingleInstanceRemoteListener<PipController,
                 IPipAnimationListener> mListener;
@@ -1129,7 +1126,8 @@
         /**
          * Invalidates this instance, preventing future calls from updating the controller.
          */
-        void invalidate() {
+        @Override
+        public void invalidate() {
             mController = null;
         }
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasks.java b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasks.java
index 2a62552..069066e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasks.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasks.java
@@ -29,13 +29,6 @@
 @ExternalThread
 public interface RecentTasks {
     /**
-     * Returns a binder that can be passed to an external process to fetch recent tasks.
-     */
-    default IRecentTasks createExternalInterface() {
-        return null;
-    }
-
-    /**
      * Gets the set of recent tasks.
      */
     default void getRecentTasks(int maxNum, int flags, int userId, Executor callbackExecutor,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java
index 02b5a35..08f3db6 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java
@@ -20,6 +20,7 @@
 import static android.content.pm.PackageManager.FEATURE_PC;
 
 import static com.android.wm.shell.common.ExecutorUtils.executeRemoteCallWithTaskPermission;
+import static com.android.wm.shell.sysui.ShellSharedConstants.KEY_EXTRA_SHELL_RECENT_TASKS;
 
 import android.app.ActivityManager;
 import android.app.ActivityTaskManager;
@@ -37,6 +38,7 @@
 import androidx.annotation.VisibleForTesting;
 
 import com.android.internal.protolog.common.ProtoLog;
+import com.android.wm.shell.common.ExternalInterfaceBinder;
 import com.android.wm.shell.common.RemoteCallable;
 import com.android.wm.shell.common.ShellExecutor;
 import com.android.wm.shell.common.SingleInstanceRemoteListener;
@@ -48,6 +50,7 @@
 import com.android.wm.shell.desktopmode.DesktopModeTaskRepository;
 import com.android.wm.shell.protolog.ShellProtoLogGroup;
 import com.android.wm.shell.sysui.ShellCommandHandler;
+import com.android.wm.shell.sysui.ShellController;
 import com.android.wm.shell.sysui.ShellInit;
 import com.android.wm.shell.util.GroupedRecentTaskInfo;
 import com.android.wm.shell.util.SplitBounds;
@@ -69,11 +72,12 @@
     private static final String TAG = RecentTasksController.class.getSimpleName();
 
     private final Context mContext;
+    private final ShellController mShellController;
     private final ShellCommandHandler mShellCommandHandler;
     private final Optional<DesktopModeTaskRepository> mDesktopModeTaskRepository;
     private final ShellExecutor mMainExecutor;
     private final TaskStackListenerImpl mTaskStackListener;
-    private final RecentTasks mImpl = new RecentTasksImpl();
+    private final RecentTasksImpl mImpl = new RecentTasksImpl();
     private final ActivityTaskManager mActivityTaskManager;
     private IRecentTasksListener mListener;
     private final boolean mIsDesktopMode;
@@ -97,6 +101,7 @@
     public static RecentTasksController create(
             Context context,
             ShellInit shellInit,
+            ShellController shellController,
             ShellCommandHandler shellCommandHandler,
             TaskStackListenerImpl taskStackListener,
             ActivityTaskManager activityTaskManager,
@@ -106,18 +111,20 @@
         if (!context.getResources().getBoolean(com.android.internal.R.bool.config_hasRecents)) {
             return null;
         }
-        return new RecentTasksController(context, shellInit, shellCommandHandler, taskStackListener,
-                activityTaskManager, desktopModeTaskRepository, mainExecutor);
+        return new RecentTasksController(context, shellInit, shellController, shellCommandHandler,
+                taskStackListener, activityTaskManager, desktopModeTaskRepository, mainExecutor);
     }
 
     RecentTasksController(Context context,
             ShellInit shellInit,
+            ShellController shellController,
             ShellCommandHandler shellCommandHandler,
             TaskStackListenerImpl taskStackListener,
             ActivityTaskManager activityTaskManager,
             Optional<DesktopModeTaskRepository> desktopModeTaskRepository,
             ShellExecutor mainExecutor) {
         mContext = context;
+        mShellController = shellController;
         mShellCommandHandler = shellCommandHandler;
         mActivityTaskManager = activityTaskManager;
         mIsDesktopMode = mContext.getPackageManager().hasSystemFeature(FEATURE_PC);
@@ -131,7 +138,13 @@
         return mImpl;
     }
 
+    private ExternalInterfaceBinder createExternalInterface() {
+        return new IRecentTasksImpl(this);
+    }
+
     private void onInit() {
+        mShellController.addExternalInterface(KEY_EXTRA_SHELL_RECENT_TASKS,
+                this::createExternalInterface, this);
         mShellCommandHandler.addDumpCallback(this::dump, this);
         mTaskStackListener.addListener(this);
         mDesktopModeTaskRepository.ifPresent(it -> it.addListener(this));
@@ -366,17 +379,6 @@
      */
     @ExternalThread
     private class RecentTasksImpl implements RecentTasks {
-        private IRecentTasksImpl mIRecentTasks;
-
-        @Override
-        public IRecentTasks createExternalInterface() {
-            if (mIRecentTasks != null) {
-                mIRecentTasks.invalidate();
-            }
-            mIRecentTasks = new IRecentTasksImpl(RecentTasksController.this);
-            return mIRecentTasks;
-        }
-
         @Override
         public void getRecentTasks(int maxNum, int flags, int userId, Executor executor,
                 Consumer<List<GroupedRecentTaskInfo>> callback) {
@@ -393,7 +395,8 @@
      * The interface for calls from outside the host process.
      */
     @BinderThread
-    private static class IRecentTasksImpl extends IRecentTasks.Stub {
+    private static class IRecentTasksImpl extends IRecentTasks.Stub
+            implements ExternalInterfaceBinder {
         private RecentTasksController mController;
         private final SingleInstanceRemoteListener<RecentTasksController,
                 IRecentTasksListener> mListener;
@@ -424,7 +427,8 @@
         /**
          * Invalidates this instance, preventing future calls from updating the controller.
          */
-        void invalidate() {
+        @Override
+        public void invalidate() {
             mController = null;
         }
 
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 e73b799..d86aadc 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
@@ -70,13 +70,6 @@
     /** Unregisters listener that gets split screen callback. */
     void unregisterSplitScreenListener(@NonNull SplitScreenListener listener);
 
-    /**
-     * Returns a binder that can be passed to an external process to manipulate SplitScreen.
-     */
-    default ISplitScreen createExternalInterface() {
-        return null;
-    }
-
     /** 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 d9c2871..c6a2b83 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
@@ -29,6 +29,7 @@
 import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_UNDEFINED;
 import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_TYPE_SIDE;
 import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_TYPE_UNDEFINED;
+import static com.android.wm.shell.sysui.ShellSharedConstants.KEY_EXTRA_SHELL_SPLIT_SCREEN;
 import static com.android.wm.shell.transition.Transitions.ENABLE_SHELL_TRANSITIONS;
 
 import android.app.ActivityManager;
@@ -71,6 +72,7 @@
 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.ExternalInterfaceBinder;
 import com.android.wm.shell.common.RemoteCallable;
 import com.android.wm.shell.common.ShellExecutor;
 import com.android.wm.shell.common.SingleInstanceRemoteListener;
@@ -214,6 +216,10 @@
         return mImpl;
     }
 
+    private ExternalInterfaceBinder createExternalInterface() {
+        return new ISplitScreenImpl(this);
+    }
+
     /**
      * This will be called after ShellTaskOrganizer has initialized/registered because of the
      * dependency order.
@@ -224,6 +230,8 @@
         mShellCommandHandler.addCommandCallback("splitscreen", mSplitScreenShellCommandHandler,
                 this);
         mShellController.addKeyguardChangeListener(this);
+        mShellController.addExternalInterface(KEY_EXTRA_SHELL_SPLIT_SCREEN,
+                this::createExternalInterface, this);
         if (mStageCoordinator == null) {
             // TODO: Multi-display
             mStageCoordinator = createStageCoordinator();
@@ -658,7 +666,6 @@
      */
     @ExternalThread
     private class SplitScreenImpl implements SplitScreen {
-        private ISplitScreenImpl mISplitScreen;
         private final ArrayMap<SplitScreenListener, Executor> mExecutors = new ArrayMap<>();
         private final SplitScreen.SplitScreenListener mListener = new SplitScreenListener() {
             @Override
@@ -704,15 +711,6 @@
         };
 
         @Override
-        public ISplitScreen createExternalInterface() {
-            if (mISplitScreen != null) {
-                mISplitScreen.invalidate();
-            }
-            mISplitScreen = new ISplitScreenImpl(SplitScreenController.this);
-            return mISplitScreen;
-        }
-
-        @Override
         public void registerSplitScreenListener(SplitScreenListener listener, Executor executor) {
             if (mExecutors.containsKey(listener)) return;
 
@@ -752,7 +750,8 @@
      * The interface for calls from outside the host process.
      */
     @BinderThread
-    private static class ISplitScreenImpl extends ISplitScreen.Stub {
+    private static class ISplitScreenImpl extends ISplitScreen.Stub
+            implements ExternalInterfaceBinder {
         private SplitScreenController mController;
         private final SingleInstanceRemoteListener<SplitScreenController,
                 ISplitScreenListener> mListener;
@@ -779,7 +778,8 @@
         /**
          * Invalidates this instance, preventing future calls from updating the controller.
          */
-        void invalidate() {
+        @Override
+        public void invalidate() {
             mController = null;
         }
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurface.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurface.java
index 76105a3..538bbec 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurface.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurface.java
@@ -22,14 +22,6 @@
  * Interface to engage starting window feature.
  */
 public interface StartingSurface {
-
-    /**
-     * Returns a binder that can be passed to an external process to manipulate starting windows.
-     */
-    default IStartingWindow createExternalInterface() {
-        return null;
-    }
-
     /**
      * Returns the background color for a starting window if existing.
      */
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingWindowController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingWindowController.java
index 379af21..0c23f10 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingWindowController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingWindowController.java
@@ -23,6 +23,7 @@
 import static android.window.StartingWindowInfo.STARTING_WINDOW_TYPE_SPLASH_SCREEN;
 
 import static com.android.wm.shell.common.ExecutorUtils.executeRemoteCallWithTaskPermission;
+import static com.android.wm.shell.sysui.ShellSharedConstants.KEY_EXTRA_SHELL_STARTING_WINDOW;
 
 import android.app.ActivityManager.RunningTaskInfo;
 import android.app.TaskInfo;
@@ -43,10 +44,12 @@
 import com.android.internal.util.function.TriConsumer;
 import com.android.launcher3.icons.IconProvider;
 import com.android.wm.shell.ShellTaskOrganizer;
+import com.android.wm.shell.common.ExternalInterfaceBinder;
 import com.android.wm.shell.common.RemoteCallable;
 import com.android.wm.shell.common.ShellExecutor;
 import com.android.wm.shell.common.SingleInstanceRemoteListener;
 import com.android.wm.shell.common.TransactionPool;
+import com.android.wm.shell.sysui.ShellController;
 import com.android.wm.shell.sysui.ShellInit;
 
 /**
@@ -76,6 +79,7 @@
     private TriConsumer<Integer, Integer, Integer> mTaskLaunchingCallback;
     private final StartingSurfaceImpl mImpl = new StartingSurfaceImpl();
     private final Context mContext;
+    private final ShellController mShellController;
     private final ShellTaskOrganizer mShellTaskOrganizer;
     private final ShellExecutor mSplashScreenExecutor;
     /**
@@ -86,12 +90,14 @@
 
     public StartingWindowController(Context context,
             ShellInit shellInit,
+            ShellController shellController,
             ShellTaskOrganizer shellTaskOrganizer,
             ShellExecutor splashScreenExecutor,
             StartingWindowTypeAlgorithm startingWindowTypeAlgorithm,
             IconProvider iconProvider,
             TransactionPool pool) {
         mContext = context;
+        mShellController = shellController;
         mShellTaskOrganizer = shellTaskOrganizer;
         mStartingSurfaceDrawer = new StartingSurfaceDrawer(context, splashScreenExecutor,
                 iconProvider, pool);
@@ -107,8 +113,14 @@
         return mImpl;
     }
 
+    private ExternalInterfaceBinder createExternalInterface() {
+        return new IStartingWindowImpl(this);
+    }
+
     private void onInit() {
         mShellTaskOrganizer.initStartingWindow(this);
+        mShellController.addExternalInterface(KEY_EXTRA_SHELL_STARTING_WINDOW,
+                this::createExternalInterface, this);
     }
 
     @Override
@@ -222,17 +234,6 @@
      * The interface for calls from outside the Shell, within the host process.
      */
     private class StartingSurfaceImpl implements StartingSurface {
-        private IStartingWindowImpl mIStartingWindow;
-
-        @Override
-        public IStartingWindowImpl createExternalInterface() {
-            if (mIStartingWindow != null) {
-                mIStartingWindow.invalidate();
-            }
-            mIStartingWindow = new IStartingWindowImpl(StartingWindowController.this);
-            return mIStartingWindow;
-        }
-
         @Override
         public int getBackgroundColor(TaskInfo taskInfo) {
             synchronized (mTaskBackgroundColors) {
@@ -256,7 +257,8 @@
      * The interface for calls from outside the host process.
      */
     @BinderThread
-    private static class IStartingWindowImpl extends IStartingWindow.Stub {
+    private static class IStartingWindowImpl extends IStartingWindow.Stub
+            implements ExternalInterfaceBinder {
         private StartingWindowController mController;
         private SingleInstanceRemoteListener<StartingWindowController,
                 IStartingWindowListener> mListener;
@@ -276,7 +278,8 @@
         /**
          * Invalidates this instance, preventing future calls from updating the controller.
          */
-        void invalidate() {
+        @Override
+        public void invalidate() {
             mController = null;
         }
 
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 5799394..fdf073f 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
@@ -23,23 +23,28 @@
 import static android.content.pm.ActivityInfo.CONFIG_SMALLEST_SCREEN_SIZE;
 import static android.content.pm.ActivityInfo.CONFIG_UI_MODE;
 
+import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_INIT;
 import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_SYSUI_EVENTS;
 
 import android.content.Context;
 import android.content.pm.ActivityInfo;
 import android.content.pm.UserInfo;
 import android.content.res.Configuration;
+import android.os.Bundle;
+import android.util.ArrayMap;
 
 import androidx.annotation.NonNull;
 import androidx.annotation.VisibleForTesting;
 
 import com.android.internal.protolog.common.ProtoLog;
+import com.android.wm.shell.common.ExternalInterfaceBinder;
 import com.android.wm.shell.common.ShellExecutor;
 import com.android.wm.shell.common.annotations.ExternalThread;
 
 import java.io.PrintWriter;
 import java.util.List;
 import java.util.concurrent.CopyOnWriteArrayList;
+import java.util.function.Supplier;
 
 /**
  * Handles event callbacks from SysUI that can be used within the Shell.
@@ -59,6 +64,11 @@
     private final CopyOnWriteArrayList<UserChangeListener> mUserChangeListeners =
             new CopyOnWriteArrayList<>();
 
+    private ArrayMap<String, Supplier<ExternalInterfaceBinder>> mExternalInterfaceSuppliers =
+            new ArrayMap<>();
+    // References to the existing interfaces, to be invalidated when they are recreated
+    private ArrayMap<String, ExternalInterfaceBinder> mExternalInterfaces = new ArrayMap<>();
+
     private Configuration mLastConfiguration;
 
 
@@ -67,6 +77,11 @@
         mShellInit = shellInit;
         mShellCommandHandler = shellCommandHandler;
         mMainExecutor = mainExecutor;
+        shellInit.addInitCallback(this::onInit, this);
+    }
+
+    private void onInit() {
+        mShellCommandHandler.addDumpCallback(this::dump, this);
     }
 
     /**
@@ -124,6 +139,47 @@
         mUserChangeListeners.remove(listener);
     }
 
+    /**
+     * Adds an interface that can be called from a remote process. This method takes a supplier
+     * because each binder reference is valid for a single process, and in multi-user mode, SysUI
+     * will request new binder instances for each instance of Launcher that it provides binders
+     * to.
+     *
+     * @param extra the key for the interface, {@see ShellSharedConstants}
+     * @param binderSupplier the supplier of the binder to pass to the external process
+     * @param callerInstance the instance of the caller, purely for logging
+     */
+    public void addExternalInterface(String extra, Supplier<ExternalInterfaceBinder> binderSupplier,
+            Object callerInstance) {
+        ProtoLog.v(WM_SHELL_INIT, "Adding external interface from %s with key %s",
+                callerInstance.getClass().getSimpleName(), extra);
+        if (mExternalInterfaceSuppliers.containsKey(extra)) {
+            throw new IllegalArgumentException("Supplier with same key already exists: "
+                    + extra);
+        }
+        mExternalInterfaceSuppliers.put(extra, binderSupplier);
+    }
+
+    /**
+     * Updates the given bundle with the set of external interfaces, invalidating the old set of
+     * binders.
+     */
+    private void createExternalInterfaces(Bundle output) {
+        // Invalidate the old binders
+        for (int i = 0; i < mExternalInterfaces.size(); i++) {
+            mExternalInterfaces.valueAt(i).invalidate();
+        }
+        mExternalInterfaces.clear();
+
+        // Create new binders for each key
+        for (int i = 0; i < mExternalInterfaceSuppliers.size(); i++) {
+            final String key = mExternalInterfaceSuppliers.keyAt(i);
+            final ExternalInterfaceBinder b = mExternalInterfaceSuppliers.valueAt(i).get();
+            mExternalInterfaces.put(key, b);
+            output.putBinder(key, b.asBinder());
+        }
+    }
+
     @VisibleForTesting
     void onConfigurationChanged(Configuration newConfig) {
         // The initial config is send on startup and doesn't trigger listener callbacks
@@ -204,6 +260,14 @@
         pw.println(innerPrefix + "mLastConfiguration=" + mLastConfiguration);
         pw.println(innerPrefix + "mKeyguardChangeListeners=" + mKeyguardChangeListeners.size());
         pw.println(innerPrefix + "mUserChangeListeners=" + mUserChangeListeners.size());
+
+        if (!mExternalInterfaces.isEmpty()) {
+            pw.println(innerPrefix + "mExternalInterfaces={");
+            for (String key : mExternalInterfaces.keySet()) {
+                pw.println(innerPrefix + "\t" + key + ": " + mExternalInterfaces.get(key));
+            }
+            pw.println(innerPrefix + "}");
+        }
     }
 
     /**
@@ -211,7 +275,6 @@
      */
     @ExternalThread
     private class ShellInterfaceImpl implements ShellInterface {
-
         @Override
         public void onInit() {
             try {
@@ -222,28 +285,6 @@
         }
 
         @Override
-        public void dump(PrintWriter pw) {
-            try {
-                mMainExecutor.executeBlocking(() -> mShellCommandHandler.dump(pw));
-            } catch (InterruptedException e) {
-                throw new RuntimeException("Failed to dump the Shell in 2s", e);
-            }
-        }
-
-        @Override
-        public boolean handleCommand(String[] args, PrintWriter pw) {
-            try {
-                boolean[] result = new boolean[1];
-                mMainExecutor.executeBlocking(() -> {
-                    result[0] = mShellCommandHandler.handleCommand(args, pw);
-                });
-                return result[0];
-            } catch (InterruptedException e) {
-                throw new RuntimeException("Failed to handle Shell command in 2s", e);
-            }
-        }
-
-        @Override
         public void onConfigurationChanged(Configuration newConfiguration) {
             mMainExecutor.execute(() ->
                     ShellController.this.onConfigurationChanged(newConfiguration));
@@ -274,5 +315,38 @@
             mMainExecutor.execute(() ->
                     ShellController.this.onUserProfilesChanged(profiles));
         }
+
+        @Override
+        public boolean handleCommand(String[] args, PrintWriter pw) {
+            try {
+                boolean[] result = new boolean[1];
+                mMainExecutor.executeBlocking(() -> {
+                    result[0] = mShellCommandHandler.handleCommand(args, pw);
+                });
+                return result[0];
+            } catch (InterruptedException e) {
+                throw new RuntimeException("Failed to handle Shell command in 2s", e);
+            }
+        }
+
+        @Override
+        public void createExternalInterfaces(Bundle bundle) {
+            try {
+                mMainExecutor.executeBlocking(() -> {
+                    ShellController.this.createExternalInterfaces(bundle);
+                });
+            } catch (InterruptedException e) {
+                throw new RuntimeException("Failed to get Shell command in 2s", e);
+            }
+        }
+
+        @Override
+        public void dump(PrintWriter pw) {
+            try {
+                mMainExecutor.executeBlocking(() -> mShellCommandHandler.dump(pw));
+            } catch (InterruptedException e) {
+                throw new RuntimeException("Failed to dump the Shell in 2s", e);
+            }
+        }
     }
 }
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 2108c82..bc5dd11 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
@@ -19,6 +19,7 @@
 import android.content.Context;
 import android.content.pm.UserInfo;
 import android.content.res.Configuration;
+import android.os.Bundle;
 
 import androidx.annotation.NonNull;
 
@@ -37,18 +38,6 @@
     default void onInit() {}
 
     /**
-     * Dumps the shell state.
-     */
-    default void dump(PrintWriter pw) {}
-
-    /**
-     * Handles a shell command.
-     */
-    default boolean handleCommand(final String[] args, PrintWriter pw) {
-        return false;
-    }
-
-    /**
      * Notifies the Shell that the configuration has changed.
      */
     default void onConfigurationChanged(Configuration newConfiguration) {}
@@ -74,4 +63,21 @@
      * Notifies the Shell when a profile belonging to the user changes.
      */
     default void onUserProfilesChanged(@NonNull List<UserInfo> profiles) {}
+
+    /**
+     * Handles a shell command.
+     */
+    default boolean handleCommand(final String[] args, PrintWriter pw) {
+        return false;
+    }
+
+    /**
+     * Updates the given {@param bundle} with the set of exposed interfaces.
+     */
+    default void createExternalInterfaces(Bundle bundle) {}
+
+    /**
+     * Dumps the shell state.
+     */
+    default void dump(PrintWriter pw) {}
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellSharedConstants.java b/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellSharedConstants.java
new file mode 100644
index 0000000..bdda6a8
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellSharedConstants.java
@@ -0,0 +1,43 @@
+/*
+ * 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;
+
+/**
+ * General shell-related constants that are shared with users of the library.
+ */
+public class ShellSharedConstants {
+    // See IPip.aidl
+    public static final String KEY_EXTRA_SHELL_PIP = "extra_shell_pip";
+    // See ISplitScreen.aidl
+    public static final String KEY_EXTRA_SHELL_SPLIT_SCREEN = "extra_shell_split_screen";
+    // See IOneHanded.aidl
+    public static final String KEY_EXTRA_SHELL_ONE_HANDED = "extra_shell_one_handed";
+    // See IShellTransitions.aidl
+    public static final String KEY_EXTRA_SHELL_SHELL_TRANSITIONS =
+            "extra_shell_shell_transitions";
+    // See IStartingWindow.aidl
+    public static final String KEY_EXTRA_SHELL_STARTING_WINDOW =
+            "extra_shell_starting_window";
+    // See IRecentTasks.aidl
+    public static final String KEY_EXTRA_SHELL_RECENT_TASKS = "extra_shell_recent_tasks";
+    // See IBackAnimation.aidl
+    public static final String KEY_EXTRA_SHELL_BACK_ANIMATION = "extra_shell_back_animation";
+    // See IFloatingTasks.aidl
+    public static final String KEY_EXTRA_SHELL_FLOATING_TASKS = "extra_shell_floating_tasks";
+    // See IDesktopMode.aidl
+    public static final String KEY_EXTRA_SHELL_DESKTOP_MODE = "extra_shell_desktop_mode";
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/ShellTransitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/ShellTransitions.java
index b34049d..da39017 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/ShellTransitions.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/ShellTransitions.java
@@ -27,14 +27,6 @@
  */
 @ExternalThread
 public interface ShellTransitions {
-
-    /**
-     * Returns a binder that can be passed to an external process to manipulate remote transitions.
-     */
-    default IShellTransitions createExternalInterface() {
-        return null;
-    }
-
     /**
      * Registers a remote transition.
      */
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
index f251e9e..d1bc738 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
@@ -30,6 +30,7 @@
 import static android.window.TransitionInfo.FLAG_STARTING_WINDOW_TRANSFER_RECIPIENT;
 
 import static com.android.wm.shell.common.ExecutorUtils.executeRemoteCallWithTaskPermission;
+import static com.android.wm.shell.sysui.ShellSharedConstants.KEY_EXTRA_SHELL_SHELL_TRANSITIONS;
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -62,11 +63,13 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.protolog.common.ProtoLog;
 import com.android.wm.shell.common.DisplayController;
+import com.android.wm.shell.common.ExternalInterfaceBinder;
 import com.android.wm.shell.common.RemoteCallable;
 import com.android.wm.shell.common.ShellExecutor;
 import com.android.wm.shell.common.TransactionPool;
 import com.android.wm.shell.common.annotations.ExternalThread;
 import com.android.wm.shell.protolog.ShellProtoLogGroup;
+import com.android.wm.shell.sysui.ShellController;
 import com.android.wm.shell.sysui.ShellInit;
 
 import java.util.ArrayList;
@@ -116,6 +119,7 @@
     private final DefaultTransitionHandler mDefaultTransitionHandler;
     private final RemoteTransitionHandler mRemoteTransitionHandler;
     private final DisplayController mDisplayController;
+    private final ShellController mShellController;
     private final ShellTransitionImpl mImpl = new ShellTransitionImpl();
 
     /** List of possible handlers. Ordered by specificity (eg. tapped back to front). */
@@ -143,6 +147,7 @@
 
     public Transitions(@NonNull Context context,
             @NonNull ShellInit shellInit,
+            @NonNull ShellController shellController,
             @NonNull WindowOrganizer organizer,
             @NonNull TransactionPool pool,
             @NonNull DisplayController displayController,
@@ -157,10 +162,14 @@
         mDefaultTransitionHandler = new DefaultTransitionHandler(context, shellInit,
                 displayController, pool, mainExecutor, mainHandler, animExecutor);
         mRemoteTransitionHandler = new RemoteTransitionHandler(mMainExecutor);
+        mShellController = shellController;
         shellInit.addInitCallback(this::onInit, this);
     }
 
     private void onInit() {
+        mShellController.addExternalInterface(KEY_EXTRA_SHELL_SHELL_TRANSITIONS,
+                this::createExternalInterface, this);
+
         // The very last handler (0 in the list) should be the default one.
         mHandlers.add(mDefaultTransitionHandler);
         ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "addHandler: Default");
@@ -194,6 +203,10 @@
         return mImpl;
     }
 
+    private ExternalInterfaceBinder createExternalInterface() {
+        return new IShellTransitionsImpl(this);
+    }
+
     @Override
     public Context getContext() {
         return mContext;
@@ -917,17 +930,6 @@
      */
     @ExternalThread
     private class ShellTransitionImpl implements ShellTransitions {
-        private IShellTransitionsImpl mIShellTransitions;
-
-        @Override
-        public IShellTransitions createExternalInterface() {
-            if (mIShellTransitions != null) {
-                mIShellTransitions.invalidate();
-            }
-            mIShellTransitions = new IShellTransitionsImpl(Transitions.this);
-            return mIShellTransitions;
-        }
-
         @Override
         public void registerRemote(@NonNull TransitionFilter filter,
                 @NonNull RemoteTransition remoteTransition) {
@@ -948,7 +950,8 @@
      * The interface for calls from outside the host process.
      */
     @BinderThread
-    private static class IShellTransitionsImpl extends IShellTransitions.Stub {
+    private static class IShellTransitionsImpl extends IShellTransitions.Stub
+            implements ExternalInterfaceBinder {
         private Transitions mTransitions;
 
         IShellTransitionsImpl(Transitions transitions) {
@@ -958,7 +961,8 @@
         /**
          * Invalidates this instance, preventing future calls from updating the controller.
          */
-        void invalidate() {
+        @Override
+        public void invalidate() {
             mTransitions = null;
         }
 
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java
index 90a3773..077e9ca 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java
@@ -63,7 +63,9 @@
 import com.android.internal.util.test.FakeSettingsProvider;
 import com.android.wm.shell.ShellTestCase;
 import com.android.wm.shell.TestShellExecutor;
+import com.android.wm.shell.sysui.ShellController;
 import com.android.wm.shell.sysui.ShellInit;
+import com.android.wm.shell.sysui.ShellSharedConstants;
 
 import org.junit.Before;
 import org.junit.Ignore;
@@ -102,6 +104,9 @@
     @Mock
     private IBackNaviAnimationController mIBackNaviAnimationController;
 
+    @Mock
+    private ShellController mShellController;
+
     private BackAnimationController mController;
 
     private int mEventTime = 0;
@@ -118,7 +123,7 @@
                 ANIMATION_ENABLED);
         mTestableLooper = TestableLooper.get(this);
         mShellInit = spy(new ShellInit(mShellExecutor));
-        mController = new BackAnimationController(mShellInit,
+        mController = new BackAnimationController(mShellInit, mShellController,
                 mShellExecutor, new Handler(mTestableLooper.getLooper()), mTransaction,
                 mActivityTaskManager, mContext,
                 mContentResolver);
@@ -175,6 +180,12 @@
     }
 
     @Test
+    public void instantiateController_addExternalInterface() {
+        verify(mShellController, times(1)).addExternalInterface(
+                eq(ShellSharedConstants.KEY_EXTRA_SHELL_BACK_ANIMATION), any(), any());
+    }
+
+    @Test
     @Ignore("b/207481538")
     public void crossActivity_screenshotAttachedAndVisible() {
         SurfaceControl screenshotSurface = new SurfaceControl();
@@ -250,7 +261,7 @@
         // Toggle the setting off
         Settings.Global.putString(mContentResolver, Settings.Global.ENABLE_BACK_ANIMATION, "0");
         ShellInit shellInit = new ShellInit(mShellExecutor);
-        mController = new BackAnimationController(shellInit,
+        mController = new BackAnimationController(shellInit, mShellController,
                 mShellExecutor, new Handler(mTestableLooper.getLooper()), mTransaction,
                 mActivityTaskManager, mContext,
                 mContentResolver);
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeControllerTest.java
index dd23d97..c850a3b 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeControllerTest.java
@@ -52,6 +52,7 @@
 import com.android.wm.shell.TestRunningTaskInfoBuilder;
 import com.android.wm.shell.TestShellExecutor;
 import com.android.wm.shell.common.ShellExecutor;
+import com.android.wm.shell.sysui.ShellController;
 import com.android.wm.shell.sysui.ShellInit;
 import com.android.wm.shell.transition.Transitions;
 
@@ -68,6 +69,8 @@
 public class DesktopModeControllerTest extends ShellTestCase {
 
     @Mock
+    private ShellController mShellController;
+    @Mock
     private ShellTaskOrganizer mShellTaskOrganizer;
     @Mock
     private RootTaskDisplayAreaOrganizer mRootTaskDisplayAreaOrganizer;
@@ -94,8 +97,8 @@
 
         mDesktopModeTaskRepository = new DesktopModeTaskRepository();
 
-        mController = new DesktopModeController(mContext, mShellInit, mShellTaskOrganizer,
-                mRootTaskDisplayAreaOrganizer, mMockTransitions,
+        mController = new DesktopModeController(mContext, mShellInit, mShellController,
+                mShellTaskOrganizer, mRootTaskDisplayAreaOrganizer, mMockTransitions,
                 mDesktopModeTaskRepository, mMockHandler, mExecutor);
 
         when(mShellTaskOrganizer.prepareClearFreeformForStandardTasks(anyInt())).thenReturn(
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/floating/FloatingTasksControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/floating/FloatingTasksControllerTest.java
index a88c837..d378a17 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/floating/FloatingTasksControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/floating/FloatingTasksControllerTest.java
@@ -52,6 +52,7 @@
 import com.android.wm.shell.sysui.ShellCommandHandler;
 import com.android.wm.shell.sysui.ShellController;
 import com.android.wm.shell.sysui.ShellInit;
+import com.android.wm.shell.sysui.ShellSharedConstants;
 
 import org.junit.After;
 import org.junit.Before;
@@ -168,6 +169,18 @@
         }
     }
 
+    @Test
+    public void onInit_addExternalInterface() {
+        if (FLOATING_TASKS_ACTUALLY_ENABLED) {
+            createController();
+            setUpTabletConfig();
+            mController.onInit();
+
+            verify(mShellController, times(1)).addExternalInterface(
+                    ShellSharedConstants.KEY_EXTRA_SHELL_FLOATING_TASKS, any(), any());
+        }
+    }
+
     //
     // Tests for floating layer, which is only available for tablets.
     //
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 cf8297e..8ad3d2a 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
@@ -51,6 +51,7 @@
 import com.android.wm.shell.sysui.ShellCommandHandler;
 import com.android.wm.shell.sysui.ShellController;
 import com.android.wm.shell.sysui.ShellInit;
+import com.android.wm.shell.sysui.ShellSharedConstants;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -176,6 +177,12 @@
     }
 
     @Test
+    public void testControllerRegisteresExternalInterface() {
+        verify(mMockShellController, times(1)).addExternalInterface(
+                eq(ShellSharedConstants.KEY_EXTRA_SHELL_ONE_HANDED), any(), 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 1e08f1e..d06fb55 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
@@ -20,6 +20,7 @@
 
 import static org.junit.Assert.assertNull;
 import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.doAnswer;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
@@ -61,6 +62,7 @@
 import com.android.wm.shell.sysui.ShellCommandHandler;
 import com.android.wm.shell.sysui.ShellController;
 import com.android.wm.shell.sysui.ShellInit;
+import com.android.wm.shell.sysui.ShellSharedConstants;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -152,6 +154,12 @@
     }
 
     @Test
+    public void instantiatePipController_registerExternalInterface() {
+        verify(mShellController, times(1)).addExternalInterface(
+                eq(ShellSharedConstants.KEY_EXTRA_SHELL_PIP), any(), any());
+    }
+
+    @Test
     public void instantiatePipController_registerUserChangeListener() {
         verify(mShellController, times(1)).addUserChangeListener(any());
     }
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java
index b8aaaa7..f6ac3ee 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java
@@ -28,6 +28,7 @@
 import static org.junit.Assert.assertTrue;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.ArgumentMatchers.isA;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.mock;
@@ -57,7 +58,9 @@
 import com.android.wm.shell.desktopmode.DesktopModeStatus;
 import com.android.wm.shell.desktopmode.DesktopModeTaskRepository;
 import com.android.wm.shell.sysui.ShellCommandHandler;
+import com.android.wm.shell.sysui.ShellController;
 import com.android.wm.shell.sysui.ShellInit;
+import com.android.wm.shell.sysui.ShellSharedConstants;
 import com.android.wm.shell.util.GroupedRecentTaskInfo;
 import com.android.wm.shell.util.SplitBounds;
 
@@ -84,6 +87,8 @@
     @Mock
     private TaskStackListenerImpl mTaskStackListener;
     @Mock
+    private ShellController mShellController;
+    @Mock
     private ShellCommandHandler mShellCommandHandler;
     @Mock
     private DesktopModeTaskRepository mDesktopModeTaskRepository;
@@ -101,7 +106,7 @@
         when(mContext.getPackageManager()).thenReturn(mock(PackageManager.class));
         mShellInit = spy(new ShellInit(mMainExecutor));
         mRecentTasksController = spy(new RecentTasksController(mContext, mShellInit,
-                mShellCommandHandler, mTaskStackListener, mActivityTaskManager,
+                mShellController, mShellCommandHandler, mTaskStackListener, mActivityTaskManager,
                 Optional.of(mDesktopModeTaskRepository), mMainExecutor));
         mShellTaskOrganizer = new ShellTaskOrganizer(mShellInit, mShellCommandHandler,
                 null /* sizeCompatUI */, Optional.empty(), Optional.of(mRecentTasksController),
@@ -121,6 +126,12 @@
     }
 
     @Test
+    public void instantiateController_addExternalInterface() {
+        verify(mShellController, times(1)).addExternalInterface(
+                eq(ShellSharedConstants.KEY_EXTRA_SHELL_RECENT_TASKS), any(), any());
+    }
+
+    @Test
     public void testAddRemoveSplitNotifyChange() {
         ActivityManager.RecentTaskInfo t1 = makeTaskInfo(1);
         ActivityManager.RecentTaskInfo t2 = makeTaskInfo(2);
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 5a68361..55883ab 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
@@ -58,6 +58,7 @@
 import com.android.wm.shell.sysui.ShellCommandHandler;
 import com.android.wm.shell.sysui.ShellController;
 import com.android.wm.shell.sysui.ShellInit;
+import com.android.wm.shell.sysui.ShellSharedConstants;
 import com.android.wm.shell.transition.Transitions;
 
 import org.junit.Before;
@@ -133,6 +134,15 @@
     }
 
     @Test
+    public void instantiateController_addExternalInterface() {
+        doReturn(mMainExecutor).when(mTaskOrganizer).getExecutor();
+        when(mDisplayController.getDisplayLayout(anyInt())).thenReturn(new DisplayLayout());
+        mSplitScreenController.onInit();
+        verify(mShellController, times(1)).addExternalInterface(
+                eq(ShellSharedConstants.KEY_EXTRA_SHELL_SPLIT_SCREEN), any(), any());
+    }
+
+    @Test
     public void testShouldAddMultipleTaskFlag_notInSplitScreen() {
         doReturn(false).when(mSplitScreenController).isSplitScreenVisible();
         doReturn(true).when(mSplitScreenController).isValidToEnterSplitScreen(any());
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/startingsurface/StartingWindowControllerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/startingsurface/StartingWindowControllerTests.java
index 35515e3..90165d1 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/startingsurface/StartingWindowControllerTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/startingsurface/StartingWindowControllerTests.java
@@ -21,6 +21,7 @@
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 
@@ -36,7 +37,9 @@
 import com.android.wm.shell.ShellTestCase;
 import com.android.wm.shell.common.ShellExecutor;
 import com.android.wm.shell.common.TransactionPool;
+import com.android.wm.shell.sysui.ShellController;
 import com.android.wm.shell.sysui.ShellInit;
+import com.android.wm.shell.sysui.ShellSharedConstants;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -56,25 +59,34 @@
 
     private @Mock Context mContext;
     private @Mock DisplayManager mDisplayManager;
-    private @Mock ShellInit mShellInit;
+    private @Mock ShellController mShellController;
     private @Mock ShellTaskOrganizer mTaskOrganizer;
     private @Mock ShellExecutor mMainExecutor;
     private @Mock StartingWindowTypeAlgorithm mTypeAlgorithm;
     private @Mock IconProvider mIconProvider;
     private @Mock TransactionPool mTransactionPool;
     private StartingWindowController mController;
+    private ShellInit mShellInit;
 
     @Before
     public void setUp() {
         MockitoAnnotations.initMocks(this);
         doReturn(mock(Display.class)).when(mDisplayManager).getDisplay(anyInt());
         doReturn(mDisplayManager).when(mContext).getSystemService(eq(DisplayManager.class));
-        mController = new StartingWindowController(mContext, mShellInit, mTaskOrganizer,
-                mMainExecutor, mTypeAlgorithm, mIconProvider, mTransactionPool);
+        mShellInit = spy(new ShellInit(mMainExecutor));
+        mController = new StartingWindowController(mContext, mShellInit, mShellController,
+                mTaskOrganizer, mMainExecutor, mTypeAlgorithm, mIconProvider, mTransactionPool);
+        mShellInit.init();
     }
 
     @Test
-    public void instantiate_addInitCallback() {
+    public void instantiateController_addInitCallback() {
         verify(mShellInit, times(1)).addInitCallback(any(), any());
     }
+
+    @Test
+    public void instantiateController_addExternalInterface() {
+        verify(mShellController, times(1)).addExternalInterface(
+                eq(ShellSharedConstants.KEY_EXTRA_SHELL_STARTING_WINDOW), any(), any());
+    }
 }
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 d6ddba9..fbc50c6 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
@@ -16,12 +16,16 @@
 
 package com.android.wm.shell.sysui;
 
+import static org.junit.Assert.assertThrows;
 import static org.junit.Assert.assertTrue;
 import static org.mockito.Mockito.mock;
 
 import android.content.Context;
 import android.content.pm.UserInfo;
 import android.content.res.Configuration;
+import android.os.Binder;
+import android.os.Bundle;
+import android.os.IBinder;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
 
@@ -30,6 +34,7 @@
 import androidx.test.platform.app.InstrumentationRegistry;
 
 import com.android.wm.shell.ShellTestCase;
+import com.android.wm.shell.common.ExternalInterfaceBinder;
 import com.android.wm.shell.common.ShellExecutor;
 
 import org.junit.After;
@@ -49,6 +54,7 @@
 public class ShellControllerTest extends ShellTestCase {
 
     private static final int TEST_USER_ID = 100;
+    private static final String EXTRA_TEST_BINDER = "test_binder";
 
     @Mock
     private ShellInit mShellInit;
@@ -81,6 +87,47 @@
     }
 
     @Test
+    public void testAddExternalInterface_ensureCallback() {
+        Binder callback = new Binder();
+        ExternalInterfaceBinder wrapper = new ExternalInterfaceBinder() {
+            @Override
+            public void invalidate() {
+                // Do nothing
+            }
+
+            @Override
+            public IBinder asBinder() {
+                return callback;
+            }
+        };
+        mController.addExternalInterface(EXTRA_TEST_BINDER, () -> wrapper, this);
+
+        Bundle b = new Bundle();
+        mController.asShell().createExternalInterfaces(b);
+        assertTrue(b.getIBinder(EXTRA_TEST_BINDER) == callback);
+    }
+
+    @Test
+    public void testAddExternalInterface_disallowDuplicateKeys() {
+        Binder callback = new Binder();
+        ExternalInterfaceBinder wrapper = new ExternalInterfaceBinder() {
+            @Override
+            public void invalidate() {
+                // Do nothing
+            }
+
+            @Override
+            public IBinder asBinder() {
+                return callback;
+            }
+        };
+        mController.addExternalInterface(EXTRA_TEST_BINDER, () -> wrapper, this);
+        assertThrows(IllegalArgumentException.class, () -> {
+            mController.addExternalInterface(EXTRA_TEST_BINDER, () -> wrapper, this);
+        });
+    }
+
+    @Test
     public void testAddUserChangeListener_ensureCallback() {
         mController.addUserChangeListener(mUserChangeListener);
 
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java
index db9136d..c764741 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java
@@ -87,7 +87,9 @@
 import com.android.wm.shell.common.DisplayLayout;
 import com.android.wm.shell.common.ShellExecutor;
 import com.android.wm.shell.common.TransactionPool;
+import com.android.wm.shell.sysui.ShellController;
 import com.android.wm.shell.sysui.ShellInit;
+import com.android.wm.shell.sysui.ShellSharedConstants;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -124,12 +126,25 @@
     @Test
     public void instantiate_addInitCallback() {
         ShellInit shellInit = mock(ShellInit.class);
-        final Transitions t = new Transitions(mContext, shellInit, mOrganizer, mTransactionPool,
-                createTestDisplayController(), mMainExecutor, mMainHandler, mAnimExecutor);
+        final Transitions t = new Transitions(mContext, shellInit, mock(ShellController.class),
+                mOrganizer, mTransactionPool, createTestDisplayController(), mMainExecutor,
+                mMainHandler, mAnimExecutor);
         verify(shellInit, times(1)).addInitCallback(any(), eq(t));
     }
 
     @Test
+    public void instantiateController_addExternalInterface() {
+        ShellInit shellInit = new ShellInit(mMainExecutor);
+        ShellController shellController = mock(ShellController.class);
+        final Transitions t = new Transitions(mContext, shellInit, shellController,
+                mOrganizer, mTransactionPool, createTestDisplayController(), mMainExecutor,
+                mMainHandler, mAnimExecutor);
+        shellInit.init();
+        verify(shellController, times(1)).addExternalInterface(
+                eq(ShellSharedConstants.KEY_EXTRA_SHELL_SHELL_TRANSITIONS), any(), any());
+    }
+
+    @Test
     public void testBasicTransitionFlow() {
         Transitions transitions = createTestTransitions();
         transitions.replaceDefaultHandlerForTest(mDefaultHandler);
@@ -1063,8 +1078,9 @@
 
     private Transitions createTestTransitions() {
         ShellInit shellInit = new ShellInit(mMainExecutor);
-        final Transitions t = new Transitions(mContext, shellInit, mOrganizer, mTransactionPool,
-                createTestDisplayController(), mMainExecutor, mMainHandler, mAnimExecutor);
+        final Transitions t = new Transitions(mContext, shellInit, mock(ShellController.class),
+                mOrganizer, mTransactionPool, createTestDisplayController(), mMainExecutor,
+                mMainHandler, mAnimExecutor);
         shellInit.init();
         return t;
     }
diff --git a/packages/SystemUI/res-keyguard/values/styles.xml b/packages/SystemUI/res-keyguard/values/styles.xml
index a1d1266..b86929e 100644
--- a/packages/SystemUI/res-keyguard/values/styles.xml
+++ b/packages/SystemUI/res-keyguard/values/styles.xml
@@ -25,7 +25,7 @@
     </style>
     <style name="Keyguard.TextView.EmergencyButton" parent="Theme.SystemUI">
         <item name="android:textColor">?androidprv:attr/textColorOnAccent</item>
-        <item name="android:textSize">14dp</item>
+        <item name="android:textSize">14sp</item>
         <item name="android:background">@drawable/kg_emergency_button_background</item>
         <item name="android:fontFamily">@*android:string/config_headlineFontFamily</item>
         <item name="android:paddingLeft">12dp</item>
diff --git a/packages/SystemUI/res/layout/media_projection_app_selector.xml b/packages/SystemUI/res/layout/media_projection_app_selector.xml
index 226bc6a..e474938 100644
--- a/packages/SystemUI/res/layout/media_projection_app_selector.xml
+++ b/packages/SystemUI/res/layout/media_projection_app_selector.xml
@@ -49,6 +49,8 @@
             android:layout_height="wrap_content"
             android:layout_width="wrap_content"
             android:textAppearance="?android:attr/textAppearanceLarge"
+            android:focusable="false"
+            android:clickable="false"
             android:gravity="center"
             android:paddingBottom="@*android:dimen/chooser_view_spacing"
             android:paddingLeft="24dp"
diff --git a/packages/SystemUI/res/layout/media_projection_recent_tasks.xml b/packages/SystemUI/res/layout/media_projection_recent_tasks.xml
index a2b3c40..31baf26 100644
--- a/packages/SystemUI/res/layout/media_projection_recent_tasks.xml
+++ b/packages/SystemUI/res/layout/media_projection_recent_tasks.xml
@@ -23,8 +23,9 @@
     >
 
     <FrameLayout
+        android:id="@+id/media_projection_recent_tasks_container"
         android:layout_width="match_parent"
-        android:layout_height="256dp">
+        android:layout_height="wrap_content">
 
         <androidx.recyclerview.widget.RecyclerView
             android:id="@+id/media_projection_recent_tasks_recycler"
diff --git a/packages/SystemUI/res/layout/media_projection_task_item.xml b/packages/SystemUI/res/layout/media_projection_task_item.xml
index 75f73cd..cfa586f 100644
--- a/packages/SystemUI/res/layout/media_projection_task_item.xml
+++ b/packages/SystemUI/res/layout/media_projection_task_item.xml
@@ -18,7 +18,9 @@
     android:layout_height="wrap_content"
     android:layout_gravity="center"
     android:gravity="center"
-    android:orientation="vertical">
+    android:orientation="vertical"
+    android:clickable="true"
+    >
 
     <ImageView
         android:id="@+id/task_icon"
@@ -27,12 +29,12 @@
         android:layout_margin="8dp"
         android:importantForAccessibility="no" />
 
-    <!-- TODO(b/240924926) use a custom view that will handle thumbnail cropping correctly -->
-    <!-- TODO(b/240924926) dynamically change the view size based on the screen size -->
-    <ImageView
+    <!-- This view size will be calculated in runtime -->
+    <com.android.systemui.mediaprojection.appselector.view.MediaProjectionTaskView
         android:id="@+id/task_thumbnail"
-        android:layout_width="100dp"
-        android:layout_height="216dp"
-        android:scaleType="centerCrop"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:clickable="false"
+        android:focusable="false"
         />
 </LinearLayout>
diff --git a/packages/SystemUI/res/values-land/dimens.xml b/packages/SystemUI/res/values-land/dimens.xml
index 9d7b01c..49ef330 100644
--- a/packages/SystemUI/res/values-land/dimens.xml
+++ b/packages/SystemUI/res/values-land/dimens.xml
@@ -59,4 +59,5 @@
     <dimen name="large_dialog_width">348dp</dimen>
 
     <dimen name="qs_panel_padding_top">@dimen/qqs_layout_margin_top</dimen>
+    <dimen name="qs_panel_padding_top_combined_headers">@dimen/qs_panel_padding_top</dimen>
 </resources>
diff --git a/packages/SystemUI/res/values-sw600dp/dimens.xml b/packages/SystemUI/res/values-sw600dp/dimens.xml
index 5dcbeb5..599bf30 100644
--- a/packages/SystemUI/res/values-sw600dp/dimens.xml
+++ b/packages/SystemUI/res/values-sw600dp/dimens.xml
@@ -68,6 +68,7 @@
     <dimen name="qs_security_footer_background_inset">0dp</dimen>
 
     <dimen name="qs_panel_padding_top">8dp</dimen>
+    <dimen name="qs_panel_padding_top_combined_headers">@dimen/qs_panel_padding_top</dimen>
 
     <!-- The width of large/content heavy dialogs (e.g. Internet, Media output, etc) -->
     <dimen name="large_dialog_width">472dp</dimen>
diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml
index 37549c9..9188ce0 100644
--- a/packages/SystemUI/res/values/config.xml
+++ b/packages/SystemUI/res/values/config.xml
@@ -337,9 +337,6 @@
      have been scrolled off-screen. -->
     <bool name="config_showNotificationShelf">true</bool>
 
-    <!-- Whether or not the notifications should always fade as they are dismissed. -->
-    <bool name="config_fadeNotificationsOnDismiss">false</bool>
-
     <!-- Whether or not the fade on the notification is based on the amount that it has been swiped
          off-screen. -->
     <bool name="config_fadeDependingOnAmountSwiped">false</bool>
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 8fdfc89..778dd45 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -559,7 +559,8 @@
     <dimen name="qs_dual_tile_padding_horizontal">6dp</dimen>
     <dimen name="qs_panel_elevation">4dp</dimen>
     <dimen name="qs_panel_padding_bottom">@dimen/footer_actions_height</dimen>
-    <dimen name="qs_panel_padding_top">80dp</dimen>
+    <dimen name="qs_panel_padding_top">48dp</dimen>
+    <dimen name="qs_panel_padding_top_combined_headers">80dp</dimen>
 
     <dimen name="qs_data_usage_text_size">14sp</dimen>
     <dimen name="qs_data_usage_usage_text_size">36sp</dimen>
@@ -1456,6 +1457,9 @@
     <dimen name="media_projection_app_selector_icon_size">32dp</dimen>
     <dimen name="media_projection_app_selector_recents_padding">16dp</dimen>
     <dimen name="media_projection_app_selector_loader_size">32dp</dimen>
+    <dimen name="media_projection_app_selector_task_rounded_corners">10dp</dimen>
+    <dimen name="media_projection_app_selector_task_icon_size">24dp</dimen>
+    <dimen name="media_projection_app_selector_task_icon_margin">8dp</dimen>
 
     <!-- Dream overlay related dimensions -->
     <dimen name="dream_overlay_status_bar_height">60dp</dimen>
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/animation/UnfoldConstantTranslateAnimator.kt b/packages/SystemUI/shared/src/com/android/systemui/shared/animation/UnfoldConstantTranslateAnimator.kt
index ffab3cd..12e0b9a 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/animation/UnfoldConstantTranslateAnimator.kt
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/animation/UnfoldConstantTranslateAnimator.kt
@@ -16,6 +16,7 @@
 package com.android.systemui.shared.animation
 
 import android.view.View
+import android.view.View.LAYOUT_DIRECTION_RTL
 import android.view.ViewGroup
 import com.android.systemui.shared.animation.UnfoldConstantTranslateAnimator.ViewIdToTranslate
 import com.android.systemui.unfold.UnfoldTransitionProgressProvider
@@ -58,9 +59,15 @@
         // progress == 0 -> -translationMax
         // progress == 1 -> 0
         val xTrans = (progress - 1f) * translationMax
+        val rtlMultiplier =
+            if (rootView.getLayoutDirection() == LAYOUT_DIRECTION_RTL) {
+                -1
+            } else {
+                1
+            }
         viewsToTranslate.forEach { (view, direction, shouldBeAnimated) ->
             if (shouldBeAnimated()) {
-                view.get()?.translationX = xTrans * direction.multiplier
+                view.get()?.translationX = xTrans * direction.multiplier * rtlMultiplier
             }
         }
     }
@@ -90,7 +97,7 @@
 
     /** Direction of the animation. */
     enum class Direction(val multiplier: Float) {
-        LEFT(-1f),
-        RIGHT(1f),
+        START(-1f),
+        END(1f),
     }
 }
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java
index 85278dd..f2742b7 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java
@@ -43,27 +43,8 @@
     public static final String KEY_EXTRA_SYSUI_PROXY = "extra_sysui_proxy";
     public static final String KEY_EXTRA_WINDOW_CORNER_RADIUS = "extra_window_corner_radius";
     public static final String KEY_EXTRA_SUPPORTS_WINDOW_CORNERS = "extra_supports_window_corners";
-    // See IPip.aidl
-    public static final String KEY_EXTRA_SHELL_PIP = "extra_shell_pip";
-    // See ISplitScreen.aidl
-    public static final String KEY_EXTRA_SHELL_SPLIT_SCREEN = "extra_shell_split_screen";
-    // See IFloatingTasks.aidl
-    public static final String KEY_EXTRA_SHELL_FLOATING_TASKS = "extra_shell_floating_tasks";
-    // See IOneHanded.aidl
-    public static final String KEY_EXTRA_SHELL_ONE_HANDED = "extra_shell_one_handed";
-    // See IShellTransitions.aidl
-    public static final String KEY_EXTRA_SHELL_SHELL_TRANSITIONS =
-            "extra_shell_shell_transitions";
-    // See IStartingWindow.aidl
-    public static final String KEY_EXTRA_SHELL_STARTING_WINDOW =
-            "extra_shell_starting_window";
     // See ISysuiUnlockAnimationController.aidl
     public static final String KEY_EXTRA_UNLOCK_ANIMATION_CONTROLLER = "unlock_animation";
-    // See IRecentTasks.aidl
-    public static final String KEY_EXTRA_RECENT_TASKS = "recent_tasks";
-    public static final String KEY_EXTRA_SHELL_BACK_ANIMATION = "extra_shell_back_animation";
-    // See IDesktopMode.aidl
-    public static final String KEY_EXTRA_SHELL_DESKTOP_MODE = "extra_shell_desktop_mode";
 
     public static final String NAV_BAR_MODE_3BUTTON_OVERLAY =
             WindowManagerPolicyConstants.NAV_BAR_MODE_3BUTTON_OVERLAY;
diff --git a/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt b/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt
index b444f4c..fd38661 100644
--- a/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt
+++ b/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt
@@ -69,7 +69,7 @@
 
     private var isCharging = false
     private var dozeAmount = 0f
-    private var isKeyguardShowing = false
+    private var isKeyguardVisible = false
 
     private val regionSamplingEnabled =
             featureFlags.isEnabled(com.android.systemui.flags.Flags.REGION_SAMPLING)
@@ -145,7 +145,7 @@
 
     private val batteryCallback = object : BatteryStateChangeCallback {
         override fun onBatteryLevelChanged(level: Int, pluggedIn: Boolean, charging: Boolean) {
-            if (isKeyguardShowing && !isCharging && charging) {
+            if (isKeyguardVisible && !isCharging && charging) {
                 clock?.animations?.charge()
             }
             isCharging = charging
@@ -168,9 +168,9 @@
     }
 
     private val keyguardUpdateMonitorCallback = object : KeyguardUpdateMonitorCallback() {
-        override fun onKeyguardVisibilityChanged(showing: Boolean) {
-            isKeyguardShowing = showing
-            if (!isKeyguardShowing) {
+        override fun onKeyguardVisibilityChanged(visible: Boolean) {
+            isKeyguardVisible = visible
+            if (!isKeyguardVisible) {
                 clock?.animations?.doze(if (isDozing) 1f else 0f)
             }
         }
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPINView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPINView.java
index 453072b..5d86ccd 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPINView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPINView.java
@@ -176,6 +176,8 @@
 
     @Override
     public void startAppearAnimation() {
+        setAlpha(1f);
+        setTranslationY(0);
         if (mAppearAnimator.isRunning()) {
             mAppearAnimator.cancel();
         }
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
index 632fcd1..353c369 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
@@ -538,7 +538,7 @@
     }
 
     /**
-     * Runs after a succsssful authentication only
+     * Runs after a successful authentication only
      */
     public void startDisappearAnimation(SecurityMode securitySelection) {
         mDisappearAnimRunning = true;
@@ -1063,6 +1063,7 @@
                 mPopup.dismiss();
                 mPopup = null;
             }
+            setupUserSwitcher();
         }
 
         @Override
@@ -1101,7 +1102,6 @@
                 return;
             }
 
-            mView.setAlpha(1f);
             mUserSwitcherViewGroup.setAlpha(0f);
             ObjectAnimator alphaAnim = ObjectAnimator.ofFloat(mUserSwitcherViewGroup, View.ALPHA,
                     1f);
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
index 57058b7..d448f40 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
@@ -427,6 +427,7 @@
 
     public void startAppearAnimation() {
         if (mCurrentSecurityMode != SecurityMode.None) {
+            mView.setAlpha(1f);
             mView.startAppearAnimation(mCurrentSecurityMode);
             getCurrentSecurityController().startAppearAnimation();
         }
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java
index c715a4e..e9f06ed 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java
@@ -212,9 +212,9 @@
         }
 
         @Override
-        public void onKeyguardVisibilityChanged(boolean showing) {
-            if (showing) {
-                if (DEBUG) Slog.v(TAG, "refresh statusview showing:" + showing);
+        public void onKeyguardVisibilityChanged(boolean visible) {
+            if (visible) {
+                if (DEBUG) Slog.v(TAG, "refresh statusview visible:true");
                 refreshTime();
             }
         }
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUnfoldTransition.kt b/packages/SystemUI/src/com/android/keyguard/KeyguardUnfoldTransition.kt
index 7d6f377..f974e27 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUnfoldTransition.kt
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUnfoldTransition.kt
@@ -20,8 +20,8 @@
 import android.view.ViewGroup
 import com.android.systemui.R
 import com.android.systemui.shared.animation.UnfoldConstantTranslateAnimator
-import com.android.systemui.shared.animation.UnfoldConstantTranslateAnimator.Direction.LEFT
-import com.android.systemui.shared.animation.UnfoldConstantTranslateAnimator.Direction.RIGHT
+import com.android.systemui.shared.animation.UnfoldConstantTranslateAnimator.Direction.END
+import com.android.systemui.shared.animation.UnfoldConstantTranslateAnimator.Direction.START
 import com.android.systemui.shared.animation.UnfoldConstantTranslateAnimator.ViewIdToTranslate
 import com.android.systemui.unfold.SysUIUnfoldScope
 import com.android.systemui.unfold.util.NaturalRotationUnfoldProgressProvider
@@ -49,13 +49,14 @@
         UnfoldConstantTranslateAnimator(
             viewsIdToTranslate =
                 setOf(
-                    ViewIdToTranslate(R.id.keyguard_status_area, LEFT, filterNever),
-                    ViewIdToTranslate(R.id.lockscreen_clock_view_large, LEFT, filterSplitShadeOnly),
-                    ViewIdToTranslate(R.id.lockscreen_clock_view, LEFT, filterNever),
+                    ViewIdToTranslate(R.id.keyguard_status_area, START, filterNever),
                     ViewIdToTranslate(
-                        R.id.notification_stack_scroller, RIGHT, filterSplitShadeOnly),
-                    ViewIdToTranslate(R.id.start_button, LEFT, filterNever),
-                    ViewIdToTranslate(R.id.end_button, RIGHT, filterNever)),
+                        R.id.lockscreen_clock_view_large, START, filterSplitShadeOnly),
+                    ViewIdToTranslate(R.id.lockscreen_clock_view, START, filterNever),
+                    ViewIdToTranslate(
+                        R.id.notification_stack_scroller, END, filterSplitShadeOnly),
+                    ViewIdToTranslate(R.id.start_button, START, filterNever),
+                    ViewIdToTranslate(R.id.end_button, END, filterNever)),
             progressProvider = unfoldProgressProvider)
     }
 
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
index d14666d..cd9089e 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
@@ -308,7 +308,8 @@
     HashMap<Integer, ServiceState> mServiceStates = new HashMap<>();
 
     private int mPhoneState;
-    private boolean mKeyguardIsVisible;
+    private boolean mKeyguardShowing;
+    private boolean mKeyguardOccluded;
     private boolean mCredentialAttempted;
     private boolean mKeyguardGoingAway;
     private boolean mGoingToSleep;
@@ -318,7 +319,6 @@
     private boolean mAuthInterruptActive;
     private boolean mNeedsSlowUnlockTransition;
     private boolean mAssistantVisible;
-    private boolean mKeyguardOccluded;
     private boolean mOccludingAppRequestingFp;
     private boolean mOccludingAppRequestingFace;
     private boolean mSecureCameraLaunched;
@@ -681,14 +681,42 @@
     }
 
     /**
-     * Updates KeyguardUpdateMonitor's internal state to know if keyguard is occluded
+     * Updates KeyguardUpdateMonitor's internal state to know if keyguard is showing and if
+     * its occluded. The keyguard is considered visible if its showing and NOT occluded.
      */
-    public void setKeyguardOccluded(boolean occluded) {
-        mKeyguardOccluded = occluded;
-        updateBiometricListeningState(BIOMETRIC_ACTION_UPDATE,
-                FACE_AUTH_UPDATED_KEYGUARD_OCCLUSION_CHANGED);
-    }
+    public void setKeyguardShowing(boolean showing, boolean occluded) {
+        final boolean occlusionChanged = mKeyguardOccluded != occluded;
+        final boolean showingChanged = mKeyguardShowing != showing;
+        if (!occlusionChanged && !showingChanged) {
+            return;
+        }
 
+        final boolean wasKeyguardVisible = isKeyguardVisible();
+        mKeyguardShowing = showing;
+        mKeyguardOccluded = occluded;
+        final boolean isKeyguardVisible = isKeyguardVisible();
+        mLogger.logKeyguardShowingChanged(showing, occluded, isKeyguardVisible);
+
+        if (isKeyguardVisible != wasKeyguardVisible) {
+            if (isKeyguardVisible) {
+                mSecureCameraLaunched = false;
+            }
+            for (int i = 0; i < mCallbacks.size(); i++) {
+                KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
+                if (cb != null) {
+                    cb.onKeyguardVisibilityChanged(isKeyguardVisible);
+                }
+            }
+        }
+
+        if (occlusionChanged) {
+            updateBiometricListeningState(BIOMETRIC_ACTION_UPDATE,
+                    FACE_AUTH_UPDATED_KEYGUARD_OCCLUSION_CHANGED);
+        } else if (showingChanged) {
+            updateBiometricListeningState(BIOMETRIC_ACTION_UPDATE,
+                    FACE_AUTH_UPDATED_KEYGUARD_VISIBILITY_CHANGED);
+        }
+    }
 
     /**
      * Request to listen for face authentication when an app is occluding keyguard.
@@ -2442,7 +2470,7 @@
         // Triggers:
         final boolean triggerActiveUnlockForAssistant = shouldTriggerActiveUnlockForAssistant();
         final boolean awakeKeyguard = mBouncerFullyShown || mUdfpsBouncerShowing
-                || (mKeyguardIsVisible && !mGoingToSleep
+                || (isKeyguardVisible() && !mGoingToSleep
                 && mStatusBarState != StatusBarState.SHADE_LOCKED);
 
         // Gates:
@@ -2518,7 +2546,7 @@
         final boolean userDoesNotHaveTrust = !getUserHasTrust(user);
         final boolean shouldListenForFingerprintAssistant = shouldListenForFingerprintAssistant();
         final boolean shouldListenKeyguardState =
-                mKeyguardIsVisible
+                isKeyguardVisible()
                         || !mDeviceInteractive
                         || (mBouncerIsOrWillBeShowing && !mKeyguardGoingAway)
                         || mGoingToSleep
@@ -2567,7 +2595,7 @@
                     mFingerprintLockedOut,
                     mGoingToSleep,
                     mKeyguardGoingAway,
-                    mKeyguardIsVisible,
+                    isKeyguardVisible(),
                     mKeyguardOccluded,
                     mOccludingAppRequestingFp,
                     mIsPrimaryUser,
@@ -2589,7 +2617,7 @@
         }
 
         final boolean statusBarShadeLocked = mStatusBarState == StatusBarState.SHADE_LOCKED;
-        final boolean awakeKeyguard = mKeyguardIsVisible && mDeviceInteractive
+        final boolean awakeKeyguard = isKeyguardVisible() && mDeviceInteractive
                 && !statusBarShadeLocked;
         final int user = getCurrentUser();
         final int strongAuth = mStrongAuthTracker.getStrongAuthForUser(user);
@@ -3146,32 +3174,18 @@
         callbacksRefreshCarrierInfo();
     }
 
+    /**
+     * Whether the keyguard is showing and not occluded.
+     */
     public boolean isKeyguardVisible() {
-        return mKeyguardIsVisible;
+        return isKeyguardShowing() && !mKeyguardOccluded;
     }
 
     /**
-     * Notifies that the visibility state of Keyguard has changed.
-     *
-     * <p>Needs to be called from the main thread.
+     * Whether the keyguard is showing. It may still be occluded and not visible.
      */
-    public void onKeyguardVisibilityChanged(boolean showing) {
-        Assert.isMainThread();
-        mLogger.logKeyguardVisibilityChanged(showing);
-        mKeyguardIsVisible = showing;
-
-        if (showing) {
-            mSecureCameraLaunched = false;
-        }
-
-        for (int i = 0; i < mCallbacks.size(); i++) {
-            KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
-            if (cb != null) {
-                cb.onKeyguardVisibilityChangedRaw(showing);
-            }
-        }
-        updateBiometricListeningState(BIOMETRIC_ACTION_UPDATE,
-                FACE_AUTH_UPDATED_KEYGUARD_VISIBILITY_CHANGED);
+    public boolean isKeyguardShowing() {
+        return mKeyguardShowing;
     }
 
     /**
@@ -3380,7 +3394,7 @@
         callback.onTimeChanged();
         callback.onPhoneStateChanged(mPhoneState);
         callback.onRefreshCarrierInfo();
-        callback.onKeyguardVisibilityChangedRaw(mKeyguardIsVisible);
+        callback.onKeyguardVisibilityChanged(isKeyguardVisible());
         callback.onTelephonyCapable(mTelephonyCapable);
 
         for (Entry<Integer, SimData> data : mSimDatas.entrySet()) {
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java
index 7a42803..bc5ab88 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java
@@ -16,7 +16,6 @@
 package com.android.keyguard;
 
 import android.hardware.biometrics.BiometricSourceType;
-import android.os.SystemClock;
 import android.telephony.TelephonyManager;
 import android.view.WindowManagerPolicyConstants;
 
@@ -32,10 +31,6 @@
  */
 public class KeyguardUpdateMonitorCallback {
 
-    private static final long VISIBILITY_CHANGED_COLLAPSE_MS = 1000;
-    private long mVisibilityChangedCalled;
-    private boolean mShowing;
-
     /**
      * Called when the battery status changes, e.g. when plugged in or unplugged, charge
      * level, etc. changes.
@@ -75,21 +70,6 @@
     public void onPhoneStateChanged(int phoneState) { }
 
     /**
-     * Called when the visibility of the keyguard changes.
-     * @param showing Indicates if the keyguard is now visible.
-     */
-    public void onKeyguardVisibilityChanged(boolean showing) { }
-
-    public void onKeyguardVisibilityChangedRaw(boolean showing) {
-        final long now = SystemClock.elapsedRealtime();
-        if (showing == mShowing
-                && (now - mVisibilityChangedCalled) < VISIBILITY_CHANGED_COLLAPSE_MS) return;
-        onKeyguardVisibilityChanged(showing);
-        mVisibilityChangedCalled = now;
-        mShowing = showing;
-    }
-
-    /**
      * Called when the keyguard enters or leaves bouncer mode.
      * @param bouncerIsOrWillBeShowing if true, keyguard is showing the bouncer or transitioning
      *                                 from/to bouncer mode.
@@ -97,6 +77,12 @@
     public void onKeyguardBouncerStateChanged(boolean bouncerIsOrWillBeShowing) { }
 
     /**
+     * Called when the keyguard visibility changes.
+     * @param visible whether the keyguard is showing and is NOT occluded
+     */
+    public void onKeyguardVisibilityChanged(boolean visible) { }
+
+    /**
      * Called when the keyguard fully transitions to the bouncer or is no longer the bouncer
      * @param bouncerIsFullyShowing if true, keyguard is fully showing the bouncer
      */
diff --git a/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java b/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java
index 2a36676..c41b752 100644
--- a/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java
@@ -447,14 +447,6 @@
     private final KeyguardUpdateMonitorCallback mKeyguardUpdateMonitorCallback =
             new KeyguardUpdateMonitorCallback() {
                 @Override
-                public void onKeyguardVisibilityChanged(boolean showing) {
-                    // reset mIsBouncerShowing state in case it was preemptively set
-                    // onLongPress
-                    mIsBouncerShowing = mKeyguardViewController.isBouncerShowing();
-                    updateVisibility();
-                }
-
-                @Override
                 public void onKeyguardBouncerStateChanged(boolean bouncer) {
                     mIsBouncerShowing = bouncer;
                     updateVisibility();
@@ -507,6 +499,11 @@
             // If biometrics were removed, local vars mCanDismissLockScreen and
             // mUserUnlockedWithBiometric may not be updated.
             mCanDismissLockScreen = mKeyguardStateController.canDismissLockScreen();
+
+            // reset mIsBouncerShowing state in case it was preemptively set
+            // onLongPress
+            mIsBouncerShowing = mKeyguardViewController.isBouncerShowing();
+
             updateKeyguardShowing();
             if (mIsKeyguardShowing) {
                 mUserUnlockedWithBiometric =
diff --git a/packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt b/packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt
index bf9f4c8..54cec71 100644
--- a/packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt
+++ b/packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt
@@ -170,8 +170,14 @@
         logBuffer.log(TAG, VERBOSE, { str1 = "$model" }, { str1!! })
     }
 
-    fun logKeyguardVisibilityChanged(showing: Boolean) {
-        logBuffer.log(TAG, DEBUG, { bool1 = showing }, { "onKeyguardVisibilityChanged($bool1)" })
+    fun logKeyguardShowingChanged(showing: Boolean, occluded: Boolean, visible: Boolean) {
+        logBuffer.log(TAG, DEBUG, {
+            bool1 = showing
+            bool2 = occluded
+            bool3 = visible
+        }, {
+            "keyguardShowingChanged(showing=$bool1 occluded=$bool2 visible=$bool3)"
+        })
     }
 
     fun logMissingSupervisorAppError(userId: Int) {
diff --git a/packages/SystemUI/src/com/android/systemui/SwipeHelper.java b/packages/SystemUI/src/com/android/systemui/SwipeHelper.java
index fe6dbe5..873a695 100644
--- a/packages/SystemUI/src/com/android/systemui/SwipeHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/SwipeHelper.java
@@ -38,6 +38,8 @@
 import android.view.ViewConfiguration;
 import android.view.accessibility.AccessibilityEvent;
 
+import androidx.annotation.VisibleForTesting;
+
 import com.android.systemui.animation.Interpolators;
 import com.android.systemui.flags.FeatureFlags;
 import com.android.systemui.flags.Flags;
@@ -66,7 +68,7 @@
     private static final int MAX_DISMISS_VELOCITY = 4000; // dp/sec
     private static final int SNAP_ANIM_LEN = SLOW_ANIMATIONS ? 1000 : 150; // ms
 
-    static final float SWIPE_PROGRESS_FADE_END = 0.5f; // fraction of thumbnail width
+    public static final float SWIPE_PROGRESS_FADE_END = 0.6f; // fraction of thumbnail width
                                               // beyond which swipe progress->0
     public static final float SWIPED_FAR_ENOUGH_SIZE_FRACTION = 0.6f;
     static final float MAX_SCROLL_SIZE_FRACTION = 0.3f;
@@ -235,7 +237,11 @@
         return Math.min(Math.max(mMinSwipeProgress, result), mMaxSwipeProgress);
     }
 
-    private float getSwipeAlpha(float progress) {
+    /**
+     * Returns the alpha value depending on the progress of the swipe.
+     */
+    @VisibleForTesting
+    public float getSwipeAlpha(float progress) {
         if (mFadeDependingOnAmountSwiped) {
             // The more progress has been fade, the lower the alpha value so that the view fades.
             return Math.max(1 - progress, 0);
@@ -260,7 +266,7 @@
                         animView.setLayerType(View.LAYER_TYPE_NONE, null);
                     }
                 }
-                animView.setAlpha(getSwipeAlpha(swipeProgress));
+                updateSwipeProgressAlpha(animView, getSwipeAlpha(swipeProgress));
             }
         }
         invalidateGlobalRegion(animView);
@@ -561,6 +567,14 @@
         mCallback.onChildSnappedBack(animView, targetLeft);
     }
 
+
+    /**
+     * Called to update the content alpha while the view is swiped
+     */
+    protected void updateSwipeProgressAlpha(View animView, float alpha) {
+        animView.setAlpha(alpha);
+    }
+
     /**
      * Give the swipe helper itself a chance to do something on snap back so NSSL doesn't have
      * to tell us what to do
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuController.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuController.java
index 11353f6..403941f 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuController.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuController.java
@@ -61,8 +61,8 @@
         }
 
         @Override
-        public void onKeyguardVisibilityChanged(boolean showing) {
-            mIsKeyguardVisible = showing;
+        public void onKeyguardVisibilityChanged(boolean visible) {
+            mIsKeyguardVisible = visible;
             handleFloatingMenuVisibility(mIsKeyguardVisible, mBtnMode, mBtnTargets);
         }
 
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt
index 4fee083..4363b88 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt
@@ -117,7 +117,7 @@
     }
 
     fun showUnlockRipple(biometricSourceType: BiometricSourceType?) {
-        if (!(keyguardUpdateMonitor.isKeyguardVisible || keyguardUpdateMonitor.isDreaming) ||
+        if (!keyguardStateController.isShowing ||
             keyguardUpdateMonitor.userNeedsStrongAuth()) {
             return
         }
diff --git a/packages/SystemUI/src/com/android/systemui/broadcast/ActionReceiver.kt b/packages/SystemUI/src/com/android/systemui/broadcast/ActionReceiver.kt
index ca36375..379c819 100644
--- a/packages/SystemUI/src/com/android/systemui/broadcast/ActionReceiver.kt
+++ b/packages/SystemUI/src/com/android/systemui/broadcast/ActionReceiver.kt
@@ -52,7 +52,7 @@
     private val userId: Int,
     private val registerAction: BroadcastReceiver.(IntentFilter) -> Unit,
     private val unregisterAction: BroadcastReceiver.() -> Unit,
-    private val bgExecutor: Executor,
+    private val workerExecutor: Executor,
     private val logger: BroadcastDispatcherLogger,
     private val testPendingRemovalAction: (BroadcastReceiver, Int) -> Boolean
 ) : BroadcastReceiver(), Dumpable {
@@ -112,7 +112,7 @@
         val id = index.getAndIncrement()
         logger.logBroadcastReceived(id, userId, intent)
         // Immediately return control to ActivityManager
-        bgExecutor.execute {
+        workerExecutor.execute {
             receiverDatas.forEach {
                 if (it.filter.matchCategories(intent.categories) == null &&
                     !testPendingRemovalAction(it.receiver, userId)) {
@@ -138,4 +138,4 @@
             println("Categories: ${activeCategories.joinToString(", ")}")
         }
     }
-}
\ No newline at end of file
+}
diff --git a/packages/SystemUI/src/com/android/systemui/broadcast/BroadcastDispatcher.kt b/packages/SystemUI/src/com/android/systemui/broadcast/BroadcastDispatcher.kt
index eb8cb47..537cbc5 100644
--- a/packages/SystemUI/src/com/android/systemui/broadcast/BroadcastDispatcher.kt
+++ b/packages/SystemUI/src/com/android/systemui/broadcast/BroadcastDispatcher.kt
@@ -34,7 +34,8 @@
 import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
 import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
 import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.dagger.qualifiers.BroadcastRunning
+import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.dump.DumpManager
 import com.android.systemui.settings.UserTracker
 import java.io.PrintWriter
@@ -55,7 +56,6 @@
 private const val MSG_REMOVE_RECEIVER = 1
 private const val MSG_REMOVE_RECEIVER_FOR_USER = 2
 private const val TAG = "BroadcastDispatcher"
-private const val DEBUG = true
 
 /**
  * SystemUI master Broadcast Dispatcher.
@@ -73,15 +73,16 @@
 @SysUISingleton
 open class BroadcastDispatcher @Inject constructor(
     private val context: Context,
-    @Background private val bgLooper: Looper,
-    @Background private val bgExecutor: Executor,
+    @Main private val mainExecutor: Executor,
+    @BroadcastRunning private val broadcastLooper: Looper,
+    @BroadcastRunning private val broadcastExecutor: Executor,
     private val dumpManager: DumpManager,
     private val logger: BroadcastDispatcherLogger,
     private val userTracker: UserTracker,
     private val removalPendingStore: PendingRemovalStore
 ) : Dumpable {
 
-    // Only modify in BG thread
+    // Only modify in BroadcastRunning thread
     private val receiversByUser = SparseArray<UserBroadcastDispatcher>(20)
 
     fun initialize() {
@@ -148,7 +149,7 @@
         val data = ReceiverData(
                 receiver,
                 filter,
-                executor ?: context.mainExecutor,
+                executor ?: mainExecutor,
                 user ?: context.user,
                 permission
             )
@@ -181,7 +182,7 @@
         registerReceiver(
             receiver,
             filter,
-            bgExecutor,
+            broadcastExecutor,
             user,
             flags,
             permission,
@@ -246,8 +247,8 @@
             UserBroadcastDispatcher(
                 context,
                 userId,
-                bgLooper,
-                bgExecutor,
+                broadcastLooper,
+                broadcastExecutor,
                 logger,
                 removalPendingStore
             )
@@ -265,7 +266,7 @@
         ipw.decreaseIndent()
     }
 
-    private val handler = object : Handler(bgLooper) {
+    private val handler = object : Handler(broadcastLooper) {
 
         override fun handleMessage(msg: Message) {
             when (msg.what) {
diff --git a/packages/SystemUI/src/com/android/systemui/broadcast/UserBroadcastDispatcher.kt b/packages/SystemUI/src/com/android/systemui/broadcast/UserBroadcastDispatcher.kt
index 6b15188..22dc94a 100644
--- a/packages/SystemUI/src/com/android/systemui/broadcast/UserBroadcastDispatcher.kt
+++ b/packages/SystemUI/src/com/android/systemui/broadcast/UserBroadcastDispatcher.kt
@@ -16,6 +16,7 @@
 
 package com.android.systemui.broadcast
 
+import android.annotation.SuppressLint
 import android.content.BroadcastReceiver
 import android.content.Context
 import android.os.Handler
@@ -46,8 +47,8 @@
 open class UserBroadcastDispatcher(
     private val context: Context,
     private val userId: Int,
-    private val bgLooper: Looper,
-    private val bgExecutor: Executor,
+    private val workerLooper: Looper,
+    private val workerExecutor: Executor,
     private val logger: BroadcastDispatcherLogger,
     private val removalPendingStore: PendingRemovalStore
 ) : Dumpable {
@@ -66,9 +67,11 @@
         val permission: String?
     )
 
-    private val bgHandler = Handler(bgLooper)
+    private val wrongThreadErrorMsg = "This method should only be called from the worker thread " +
+            "(which is expected to be the BroadcastRunning thread)"
+    private val workerHandler = Handler(workerLooper)
 
-    // Only modify in BG thread
+    // Only modify in BroadcastRunning thread
     @VisibleForTesting
     internal val actionsToActionsReceivers = ArrayMap<ReceiverProperties, ActionReceiver>()
     private val receiverToActions = ArrayMap<BroadcastReceiver, MutableSet<String>>()
@@ -97,8 +100,7 @@
     }
 
     private fun handleRegisterReceiver(receiverData: ReceiverData, flags: Int) {
-        Preconditions.checkState(bgLooper.isCurrentThread,
-                "This method should only be called from BG thread")
+        Preconditions.checkState(workerLooper.isCurrentThread, wrongThreadErrorMsg)
         if (DEBUG) Log.w(TAG, "Register receiver: ${receiverData.receiver}")
         receiverToActions
                 .getOrPut(receiverData.receiver, { ArraySet() })
@@ -113,6 +115,7 @@
         logger.logReceiverRegistered(userId, receiverData.receiver, flags)
     }
 
+    @SuppressLint("RegisterReceiverViaContextDetector")
     @VisibleForTesting
     internal open fun createActionReceiver(
         action: String,
@@ -128,7 +131,7 @@
                             UserHandle.of(userId),
                             it,
                             permission,
-                            bgHandler,
+                            workerHandler,
                             flags
                     )
                     logger.logContextReceiverRegistered(userId, flags, it)
@@ -143,15 +146,14 @@
                                 IllegalStateException(e))
                     }
                 },
-                bgExecutor,
+                workerExecutor,
                 logger,
                 removalPendingStore::isPendingRemoval
         )
     }
 
     private fun handleUnregisterReceiver(receiver: BroadcastReceiver) {
-        Preconditions.checkState(bgLooper.isCurrentThread,
-                "This method should only be called from BG thread")
+        Preconditions.checkState(workerLooper.isCurrentThread, wrongThreadErrorMsg)
         if (DEBUG) Log.w(TAG, "Unregister receiver: $receiver")
         receiverToActions.getOrDefault(receiver, mutableSetOf()).forEach {
             actionsToActionsReceivers.forEach { (key, value) ->
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
index 06dbab9..d70b971 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
@@ -43,7 +43,7 @@
 import com.android.systemui.fragments.FragmentService;
 import com.android.systemui.keyguard.data.BouncerViewModule;
 import com.android.systemui.log.dagger.LogModule;
-import com.android.systemui.media.dagger.MediaProjectionModule;
+import com.android.systemui.mediaprojection.appselector.MediaProjectionModule;
 import com.android.systemui.model.SysUiState;
 import com.android.systemui.navigationbar.NavigationBarComponent;
 import com.android.systemui.people.PeopleModule;
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/qualifiers/BroadcastRunning.java b/packages/SystemUI/src/com/android/systemui/dagger/qualifiers/BroadcastRunning.java
new file mode 100644
index 0000000..5f8e540
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/dagger/qualifiers/BroadcastRunning.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.dagger.qualifiers;
+
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+
+import javax.inject.Qualifier;
+
+@Qualifier
+@Documented
+@Retention(RUNTIME)
+public @interface BroadcastRunning {
+}
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeLog.java b/packages/SystemUI/src/com/android/systemui/doze/DozeLog.java
index 8ae305b..2e51b51 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeLog.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeLog.java
@@ -431,8 +431,8 @@
         }
 
         @Override
-        public void onKeyguardVisibilityChanged(boolean showing) {
-            traceKeyguard(showing);
+        public void onKeyguardVisibilityChanged(boolean visible) {
+            traceKeyguard(visible);
         }
     };
 
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeLogger.kt b/packages/SystemUI/src/com/android/systemui/doze/DozeLogger.kt
index 21a2c3b..cc57662 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeLogger.kt
@@ -155,11 +155,11 @@
         })
     }
 
-    fun logKeyguardVisibilityChange(isShowing: Boolean) {
+    fun logKeyguardVisibilityChange(isVisible: Boolean) {
         buffer.log(TAG, INFO, {
-            bool1 = isShowing
+            bool1 = isVisible
         }, {
-            "Keyguard visibility change, isShowing=$bool1"
+            "Keyguard visibility change, isVisible=$bool1"
         })
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationLayoutEngine.java b/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationLayoutEngine.java
index 9cd149b..5694f6d 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationLayoutEngine.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationLayoutEngine.java
@@ -18,7 +18,7 @@
 
 import static com.android.systemui.dreams.complication.dagger.ComplicationHostViewModule.COMPLICATIONS_FADE_IN_DURATION;
 import static com.android.systemui.dreams.complication.dagger.ComplicationHostViewModule.COMPLICATIONS_FADE_OUT_DURATION;
-import static com.android.systemui.dreams.complication.dagger.ComplicationHostViewModule.COMPLICATION_MARGIN;
+import static com.android.systemui.dreams.complication.dagger.ComplicationHostViewModule.COMPLICATION_MARGIN_DEFAULT;
 import static com.android.systemui.dreams.complication.dagger.ComplicationHostViewModule.SCOPED_COMPLICATIONS_LAYOUT;
 
 import android.animation.Animator;
@@ -67,7 +67,7 @@
         private final Parent mParent;
         @Complication.Category
         private final int mCategory;
-        private final int mMargin;
+        private final int mDefaultMargin;
 
         /**
          * Default constructor. {@link Parent} allows for the {@link ViewEntry}'s surrounding
@@ -75,7 +75,7 @@
          */
         ViewEntry(View view, ComplicationLayoutParams layoutParams,
                 TouchInsetManager.TouchInsetSession touchSession, int category, Parent parent,
-                int margin) {
+                int defaultMargin) {
             mView = view;
             // Views that are generated programmatically do not have a unique id assigned to them
             // at construction. A new id is assigned here to enable ConstraintLayout relative
@@ -86,7 +86,7 @@
             mTouchInsetSession = touchSession;
             mCategory = category;
             mParent = parent;
-            mMargin = margin;
+            mDefaultMargin = defaultMargin;
 
             touchSession.addViewToTracking(mView);
         }
@@ -195,18 +195,19 @@
                 }
 
                 if (!isRoot) {
+                    final int margin = mLayoutParams.getMargin(mDefaultMargin);
                     switch(direction) {
                         case ComplicationLayoutParams.DIRECTION_DOWN:
-                            params.setMargins(0, mMargin, 0, 0);
+                            params.setMargins(0, margin, 0, 0);
                             break;
                         case ComplicationLayoutParams.DIRECTION_UP:
-                            params.setMargins(0, 0, 0, mMargin);
+                            params.setMargins(0, 0, 0, margin);
                             break;
                         case ComplicationLayoutParams.DIRECTION_END:
-                            params.setMarginStart(mMargin);
+                            params.setMarginStart(margin);
                             break;
                         case ComplicationLayoutParams.DIRECTION_START:
-                            params.setMarginEnd(mMargin);
+                            params.setMarginEnd(margin);
                             break;
                     }
                 }
@@ -263,7 +264,7 @@
             private final ComplicationLayoutParams mLayoutParams;
             private final int mCategory;
             private Parent mParent;
-            private int mMargin;
+            private int mDefaultMargin;
 
             Builder(View view, TouchInsetManager.TouchInsetSession touchSession,
                     ComplicationLayoutParams lp, @Complication.Category int category) {
@@ -302,8 +303,8 @@
              * Sets the margin that will be applied in the direction the complication is laid out
              * towards.
              */
-            Builder setMargin(int margin) {
-                mMargin = margin;
+            Builder setDefaultMargin(int margin) {
+                mDefaultMargin = margin;
                 return this;
             }
 
@@ -312,7 +313,7 @@
              */
             ViewEntry build() {
                 return new ViewEntry(mView, mLayoutParams, mTouchSession, mCategory, mParent,
-                        mMargin);
+                        mDefaultMargin);
             }
         }
 
@@ -472,7 +473,7 @@
     }
 
     private final ConstraintLayout mLayout;
-    private final int mMargin;
+    private final int mDefaultMargin;
     private final HashMap<ComplicationId, ViewEntry> mEntries = new HashMap<>();
     private final HashMap<Integer, PositionGroup> mPositions = new HashMap<>();
     private final TouchInsetManager.TouchInsetSession mSession;
@@ -483,12 +484,12 @@
     /** */
     @Inject
     public ComplicationLayoutEngine(@Named(SCOPED_COMPLICATIONS_LAYOUT) ConstraintLayout layout,
-            @Named(COMPLICATION_MARGIN) int margin,
+            @Named(COMPLICATION_MARGIN_DEFAULT) int defaultMargin,
             TouchInsetManager.TouchInsetSession session,
             @Named(COMPLICATIONS_FADE_IN_DURATION) int fadeInDuration,
             @Named(COMPLICATIONS_FADE_OUT_DURATION) int fadeOutDuration) {
         mLayout = layout;
-        mMargin = margin;
+        mDefaultMargin = defaultMargin;
         mSession = session;
         mFadeInDuration = fadeInDuration;
         mFadeOutDuration = fadeOutDuration;
@@ -537,7 +538,7 @@
         }
 
         final ViewEntry.Builder entryBuilder = new ViewEntry.Builder(view, mSession, lp, category)
-                .setMargin(mMargin);
+                .setDefaultMargin(mDefaultMargin);
 
         // Add position group if doesn't already exist
         final int position = lp.getPosition();
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationLayoutParams.java b/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationLayoutParams.java
index 8e8cb72..a21eb19 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationLayoutParams.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationLayoutParams.java
@@ -51,6 +51,8 @@
     private static final int FIRST_POSITION = POSITION_TOP;
     private static final int LAST_POSITION = POSITION_END;
 
+    private static final int MARGIN_UNSPECIFIED = 0xFFFFFFFF;
+
     @Retention(RetentionPolicy.SOURCE)
     @IntDef(flag = true, prefix = { "DIRECTION_" }, value = {
             DIRECTION_UP,
@@ -77,6 +79,8 @@
 
     private final int mWeight;
 
+    private final int mMargin;
+
     private final boolean mSnapToGuide;
 
     // Do not allow specifying opposite positions
@@ -106,7 +110,24 @@
      */
     public ComplicationLayoutParams(int width, int height, @Position int position,
             @Direction int direction, int weight) {
-        this(width, height, position, direction, weight, false);
+        this(width, height, position, direction, weight, MARGIN_UNSPECIFIED, false);
+    }
+
+    /**
+     * Constructs a {@link ComplicationLayoutParams}.
+     * @param width The width {@link android.view.View.MeasureSpec} for the view.
+     * @param height The height {@link android.view.View.MeasureSpec} for the view.
+     * @param position The place within the parent container where the view should be positioned.
+     * @param direction The direction the view should be laid out from either the parent container
+     *                  or preceding view.
+     * @param weight The weight that should be considered for this view when compared to other
+     *               views. This has an impact on the placement of the view but not the rendering of
+     *               the view.
+     * @param margin The margin to apply between complications.
+     */
+    public ComplicationLayoutParams(int width, int height, @Position int position,
+            @Direction int direction, int weight, int margin) {
+        this(width, height, position, direction, weight, margin, false);
     }
 
     /**
@@ -127,6 +148,28 @@
      */
     public ComplicationLayoutParams(int width, int height, @Position int position,
             @Direction int direction, int weight, boolean snapToGuide) {
+        this(width, height, position, direction, weight, MARGIN_UNSPECIFIED, snapToGuide);
+    }
+
+    /**
+     * Constructs a {@link ComplicationLayoutParams}.
+     * @param width The width {@link android.view.View.MeasureSpec} for the view.
+     * @param height The height {@link android.view.View.MeasureSpec} for the view.
+     * @param position The place within the parent container where the view should be positioned.
+     * @param direction The direction the view should be laid out from either the parent container
+     *                  or preceding view.
+     * @param weight The weight that should be considered for this view when compared to other
+     *               views. This has an impact on the placement of the view but not the rendering of
+     *               the view.
+     * @param margin The margin to apply between complications.
+     * @param snapToGuide When set to {@code true}, the dimension perpendicular to the direction
+     *                    will be automatically set to align with a predetermined guide for that
+     *                    side. For example, if the complication is aligned to the top end and
+     *                    direction is down, then the width of the complication will be set to span
+     *                    from the end of the parent to the guide.
+     */
+    public ComplicationLayoutParams(int width, int height, @Position int position,
+            @Direction int direction, int weight, int margin, boolean snapToGuide) {
         super(width, height);
 
         if (!validatePosition(position)) {
@@ -142,6 +185,8 @@
 
         mWeight = weight;
 
+        mMargin = margin;
+
         mSnapToGuide = snapToGuide;
     }
 
@@ -153,6 +198,7 @@
         mPosition = source.mPosition;
         mDirection = source.mDirection;
         mWeight = source.mWeight;
+        mMargin = source.mMargin;
         mSnapToGuide = source.mSnapToGuide;
     }
 
@@ -215,6 +261,14 @@
     }
 
     /**
+     * Returns the margin to apply between complications, or the given default if no margin is
+     * specified.
+     */
+    public int getMargin(int defaultMargin) {
+        return mMargin == MARGIN_UNSPECIFIED ? defaultMargin : mMargin;
+    }
+
+    /**
      * Returns whether the complication's dimension perpendicular to direction should be
      * automatically set.
      */
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/ComplicationHostViewModule.java b/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/ComplicationHostViewModule.java
index 11d89d2..c9fecc9 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/ComplicationHostViewModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/ComplicationHostViewModule.java
@@ -37,7 +37,7 @@
 @Module
 public abstract class ComplicationHostViewModule {
     public static final String SCOPED_COMPLICATIONS_LAYOUT = "scoped_complications_layout";
-    public static final String COMPLICATION_MARGIN = "complication_margin";
+    public static final String COMPLICATION_MARGIN_DEFAULT = "complication_margin_default";
     public static final String COMPLICATIONS_FADE_OUT_DURATION = "complications_fade_out_duration";
     public static final String COMPLICATIONS_FADE_IN_DURATION = "complications_fade_in_duration";
     public static final String COMPLICATIONS_RESTORE_TIMEOUT = "complication_restore_timeout";
@@ -58,7 +58,7 @@
     }
 
     @Provides
-    @Named(COMPLICATION_MARGIN)
+    @Named(COMPLICATION_MARGIN_DEFAULT)
     @DreamOverlayComponent.DreamOverlayScope
     static int providesComplicationPadding(@Main Resources resources) {
         return resources.getDimensionPixelSize(R.dimen.dream_overlay_complication_margin);
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/RegisteredComplicationsModule.java b/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/RegisteredComplicationsModule.java
index 759d6ec..7d2ce51 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/RegisteredComplicationsModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/RegisteredComplicationsModule.java
@@ -59,11 +59,11 @@
     @Named(DREAM_CLOCK_TIME_COMPLICATION_LAYOUT_PARAMS)
     static ComplicationLayoutParams provideClockTimeLayoutParams() {
         return new ComplicationLayoutParams(0,
-            ViewGroup.LayoutParams.WRAP_CONTENT,
-            ComplicationLayoutParams.POSITION_TOP
-                    | ComplicationLayoutParams.POSITION_START,
-            ComplicationLayoutParams.DIRECTION_DOWN,
-            DREAM_CLOCK_TIME_COMPLICATION_WEIGHT);
+                ViewGroup.LayoutParams.WRAP_CONTENT,
+                ComplicationLayoutParams.POSITION_TOP
+                        | ComplicationLayoutParams.POSITION_START,
+                ComplicationLayoutParams.DIRECTION_DOWN,
+                DREAM_CLOCK_TIME_COMPLICATION_WEIGHT);
     }
 
     /**
@@ -73,12 +73,12 @@
     @Named(DREAM_HOME_CONTROLS_CHIP_LAYOUT_PARAMS)
     static ComplicationLayoutParams provideHomeControlsChipLayoutParams(@Main Resources res) {
         return new ComplicationLayoutParams(
-            res.getDimensionPixelSize(R.dimen.keyguard_affordance_fixed_width),
-            res.getDimensionPixelSize(R.dimen.keyguard_affordance_fixed_height),
-            ComplicationLayoutParams.POSITION_BOTTOM
-                    | ComplicationLayoutParams.POSITION_START,
-            ComplicationLayoutParams.DIRECTION_END,
-            DREAM_HOME_CONTROLS_CHIP_COMPLICATION_WEIGHT);
+                res.getDimensionPixelSize(R.dimen.keyguard_affordance_fixed_width),
+                res.getDimensionPixelSize(R.dimen.keyguard_affordance_fixed_height),
+                ComplicationLayoutParams.POSITION_BOTTOM
+                        | ComplicationLayoutParams.POSITION_START,
+                ComplicationLayoutParams.DIRECTION_END,
+                DREAM_HOME_CONTROLS_CHIP_COMPLICATION_WEIGHT);
     }
 
     /**
@@ -103,11 +103,12 @@
     @Named(DREAM_SMARTSPACE_LAYOUT_PARAMS)
     static ComplicationLayoutParams provideSmartspaceLayoutParams() {
         return new ComplicationLayoutParams(0,
-            ViewGroup.LayoutParams.WRAP_CONTENT,
-            ComplicationLayoutParams.POSITION_TOP
-                    | ComplicationLayoutParams.POSITION_START,
-            ComplicationLayoutParams.DIRECTION_DOWN,
-            DREAM_SMARTSPACE_COMPLICATION_WEIGHT,
-            true /*snapToGuide*/);
+                ViewGroup.LayoutParams.WRAP_CONTENT,
+                ComplicationLayoutParams.POSITION_TOP
+                        | ComplicationLayoutParams.POSITION_START,
+                ComplicationLayoutParams.DIRECTION_DOWN,
+                DREAM_SMARTSPACE_COMPLICATION_WEIGHT,
+                0,
+                true /*snapToGuide*/);
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.java b/packages/SystemUI/src/com/android/systemui/flags/Flags.java
index 978aaa0..7e08da7 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.java
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.java
@@ -71,7 +71,9 @@
     public static final UnreleasedFlag NOTIFICATION_MEMORY_MONITOR_ENABLED = new UnreleasedFlag(112,
             false);
 
-    // next id: 112
+    public static final UnreleasedFlag NOTIFICATION_DISMISSAL_FADE = new UnreleasedFlag(113, true);
+
+    // next id: 114
 
     /***************************************/
     // 200 - keyguard/lockscreen
@@ -228,7 +230,8 @@
             new ReleasedFlag(1000);
     public static final ReleasedFlag DOCK_SETUP_ENABLED = new ReleasedFlag(1001);
 
-    public static final UnreleasedFlag ROUNDED_BOX_RIPPLE = new UnreleasedFlag(1002, false);
+    public static final UnreleasedFlag ROUNDED_BOX_RIPPLE =
+            new UnreleasedFlag(1002, /* teamfood= */ true);
 
     // 1100 - windowing
     @Keep
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index 2bfdce0..da0b910 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -511,9 +511,9 @@
     KeyguardUpdateMonitorCallback mUpdateCallback = new KeyguardUpdateMonitorCallback() {
 
         @Override
-        public void onKeyguardVisibilityChanged(boolean showing) {
+        public void onKeyguardVisibilityChanged(boolean visible) {
             synchronized (KeyguardViewMediator.this) {
-                if (!showing && mPendingPinLock) {
+                if (!visible && mPendingPinLock) {
                     Log.i(TAG, "PIN lock requested, starting keyguard");
 
                     // Bring the keyguard back in order to show the PIN lock
@@ -1806,7 +1806,6 @@
 
             if (mOccluded != isOccluded) {
                 mOccluded = isOccluded;
-                mUpdateMonitor.setKeyguardOccluded(isOccluded);
                 mKeyguardViewControllerLazy.get().setOccluded(isOccluded, animate
                         && mDeviceInteractive);
                 adjustStatusBarLocked();
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaProjectionAppSelectorActivity.kt b/packages/SystemUI/src/com/android/systemui/media/MediaProjectionAppSelectorActivity.kt
index ea57806..1ac2a07 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaProjectionAppSelectorActivity.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaProjectionAppSelectorActivity.kt
@@ -16,8 +16,8 @@
 package com.android.systemui.media
 
 import android.app.ActivityOptions
-import android.app.IActivityTaskManager
 import android.content.Intent
+import android.content.res.Configuration
 import android.media.projection.IMediaProjection
 import android.media.projection.MediaProjectionManager.EXTRA_MEDIA_PROJECTION
 import android.os.Binder
@@ -25,87 +25,73 @@
 import android.os.IBinder
 import android.os.ResultReceiver
 import android.os.UserHandle
-import android.view.LayoutInflater
-import android.view.View
 import android.view.ViewGroup
-import androidx.recyclerview.widget.LinearLayoutManager
-import androidx.recyclerview.widget.RecyclerView
 import com.android.internal.annotations.VisibleForTesting
 import com.android.internal.app.ChooserActivity
 import com.android.internal.app.ResolverListController
 import com.android.internal.app.chooser.NotSelectableTargetInfo
 import com.android.internal.app.chooser.TargetInfo
 import com.android.systemui.R
+import com.android.systemui.mediaprojection.appselector.MediaProjectionAppSelectorComponent
 import com.android.systemui.mediaprojection.appselector.MediaProjectionAppSelectorController
+import com.android.systemui.mediaprojection.appselector.MediaProjectionAppSelectorResultHandler
 import com.android.systemui.mediaprojection.appselector.MediaProjectionAppSelectorView
 import com.android.systemui.mediaprojection.appselector.data.RecentTask
-import com.android.systemui.mediaprojection.appselector.view.RecentTasksAdapter
-import com.android.systemui.mediaprojection.appselector.view.RecentTasksAdapter.RecentTaskClickListener
+import com.android.systemui.mediaprojection.appselector.view.MediaProjectionRecentsViewController
+import com.android.systemui.statusbar.policy.ConfigurationController
 import com.android.systemui.util.AsyncActivityLauncher
-import com.android.systemui.util.recycler.HorizontalSpacerItemDecoration
 import javax.inject.Inject
 
 class MediaProjectionAppSelectorActivity(
+    private val componentFactory: MediaProjectionAppSelectorComponent.Factory,
     private val activityLauncher: AsyncActivityLauncher,
-    private val activityTaskManager: IActivityTaskManager,
-    private val controller: MediaProjectionAppSelectorController,
-    private val recentTasksAdapterFactory: RecentTasksAdapter.Factory,
     /** This is used to override the dependency in a screenshot test */
     @VisibleForTesting
     private val listControllerFactory: ((userHandle: UserHandle) -> ResolverListController)?
-) : ChooserActivity(), MediaProjectionAppSelectorView, RecentTaskClickListener {
+) : ChooserActivity(), MediaProjectionAppSelectorView, MediaProjectionAppSelectorResultHandler {
 
     @Inject
     constructor(
+        componentFactory: MediaProjectionAppSelectorComponent.Factory,
         activityLauncher: AsyncActivityLauncher,
-        activityTaskManager: IActivityTaskManager,
-        controller: MediaProjectionAppSelectorController,
-        recentTasksAdapterFactory: RecentTasksAdapter.Factory,
-    ) : this(activityLauncher, activityTaskManager, controller, recentTasksAdapterFactory, null)
+    ) : this(componentFactory, activityLauncher, null)
 
-    private var recentsRoot: ViewGroup? = null
-    private var recentsProgress: View? = null
-    private var recentsRecycler: RecyclerView? = null
+    private lateinit var configurationController: ConfigurationController
+    private lateinit var controller: MediaProjectionAppSelectorController
+    private lateinit var recentsViewController: MediaProjectionRecentsViewController
 
-    override fun getLayoutResource() =
-        R.layout.media_projection_app_selector
+    override fun getLayoutResource() = R.layout.media_projection_app_selector
 
     public override fun onCreate(bundle: Bundle?) {
-        val queryIntent = Intent(Intent.ACTION_MAIN)
-            .addCategory(Intent.CATEGORY_LAUNCHER)
+        val component =
+            componentFactory.create(
+                activity = this,
+                view = this,
+                resultHandler = this
+            )
+
+        // Create a separate configuration controller for this activity as the configuration
+        // might be different from the global one
+        configurationController = component.configurationController
+        controller = component.controller
+        recentsViewController = component.recentsViewController
+
+        val queryIntent = Intent(Intent.ACTION_MAIN).apply { addCategory(Intent.CATEGORY_LAUNCHER) }
         intent.putExtra(Intent.EXTRA_INTENT, queryIntent)
 
         // TODO(b/240939253): update copies
         val title = getString(R.string.media_projection_dialog_service_title)
         intent.putExtra(Intent.EXTRA_TITLE, title)
         super.onCreate(bundle)
-        controller.init(this)
+        controller.init()
     }
 
-    private fun createRecentsView(parent: ViewGroup): ViewGroup {
-        val recentsRoot = LayoutInflater.from(this)
-            .inflate(R.layout.media_projection_recent_tasks, parent,
-                    /* attachToRoot= */ false) as ViewGroup
-
-        recentsProgress = recentsRoot.requireViewById(R.id.media_projection_recent_tasks_loader)
-        recentsRecycler = recentsRoot.requireViewById(R.id.media_projection_recent_tasks_recycler)
-        recentsRecycler?.layoutManager = LinearLayoutManager(
-            this, LinearLayoutManager.HORIZONTAL,
-            /* reverseLayout= */false
-        )
-
-        val itemDecoration = HorizontalSpacerItemDecoration(
-            resources.getDimensionPixelOffset(
-                R.dimen.media_projection_app_selector_recents_padding
-            )
-        )
-        recentsRecycler?.addItemDecoration(itemDecoration)
-
-        return recentsRoot
+    override fun onConfigurationChanged(newConfig: Configuration) {
+        super.onConfigurationChanged(newConfig)
+        configurationController.onConfigurationChanged(newConfig)
     }
 
-    override fun appliedThemeResId(): Int =
-        R.style.Theme_SystemUI_MediaProjectionAppSelector
+    override fun appliedThemeResId(): Int = R.style.Theme_SystemUI_MediaProjectionAppSelector
 
     override fun createListController(userHandle: UserHandle): ResolverListController =
         listControllerFactory?.invoke(userHandle) ?: super.createListController(userHandle)
@@ -127,9 +113,9 @@
         // is typically very fast, so we don't show any loaders.
         // We wait for the activity to be launched to make sure that the window of the activity
         // is created and ready to be captured.
-        val activityStarted = activityLauncher
-            .startActivityAsUser(intent, userHandle, activityOptions.toBundle()) {
-                onTargetActivityLaunched(launchToken)
+        val activityStarted =
+            activityLauncher.startActivityAsUser(intent, userHandle, activityOptions.toBundle()) {
+                returnSelectedApp(launchToken)
             }
 
         // Rely on the ActivityManager to pop up a dialog regarding app suspension
@@ -163,50 +149,27 @@
     }
 
     override fun bind(recentTasks: List<RecentTask>) {
-        val recents = recentsRoot ?: return
-        val progress = recentsProgress ?: return
-        val recycler = recentsRecycler ?: return
-
-        if (recentTasks.isEmpty()) {
-            recents.visibility = View.GONE
-            return
-        }
-
-        progress.visibility = View.GONE
-        recycler.visibility = View.VISIBLE
-        recents.visibility = View.VISIBLE
-
-        recycler.adapter = recentTasksAdapterFactory.create(recentTasks, this)
+        recentsViewController.bind(recentTasks)
     }
 
-    override fun onRecentAppClicked(task: RecentTask, view: View) {
-        val launchCookie = Binder()
-        val activityOptions = ActivityOptions.makeScaleUpAnimation(view, /* startX= */ 0,
-                /* startY= */ 0, view.width, view.height)
-        activityOptions.launchCookie = launchCookie
-
-        activityTaskManager.startActivityFromRecents(task.taskId, activityOptions.toBundle())
-        onTargetActivityLaunched(launchCookie)
-    }
-
-    private fun onTargetActivityLaunched(launchToken: IBinder) {
+    override fun returnSelectedApp(launchCookie: IBinder) {
         if (intent.hasExtra(EXTRA_CAPTURE_REGION_RESULT_RECEIVER)) {
             // The client requested to return the result in the result receiver instead of
             // activity result, let's send the media projection to the result receiver
-            val resultReceiver = intent
-                .getParcelableExtra(EXTRA_CAPTURE_REGION_RESULT_RECEIVER,
-                    ResultReceiver::class.java) as ResultReceiver
-            val captureRegion = MediaProjectionCaptureTarget(launchToken)
-            val data = Bundle().apply {
-                putParcelable(KEY_CAPTURE_TARGET, captureRegion)
-            }
+            val resultReceiver =
+                intent.getParcelableExtra(
+                    EXTRA_CAPTURE_REGION_RESULT_RECEIVER,
+                    ResultReceiver::class.java
+                ) as ResultReceiver
+            val captureRegion = MediaProjectionCaptureTarget(launchCookie)
+            val data = Bundle().apply { putParcelable(KEY_CAPTURE_TARGET, captureRegion) }
             resultReceiver.send(RESULT_OK, data)
         } else {
             // Return the media projection instance as activity result
             val mediaProjectionBinder = intent.getIBinderExtra(EXTRA_MEDIA_PROJECTION)
             val projection = IMediaProjection.Stub.asInterface(mediaProjectionBinder)
 
-            projection.launchCookie = launchToken
+            projection.launchCookie = launchCookie
 
             val intent = Intent()
             intent.putExtra(EXTRA_MEDIA_PROJECTION, projection.asBinder())
@@ -223,15 +186,13 @@
     override fun shouldShowContentPreview() = false
 
     override fun createContentPreviewView(parent: ViewGroup): ViewGroup =
-            recentsRoot ?: createRecentsView(parent).also {
-                recentsRoot = it
-            }
+        recentsViewController.createView(parent)
 
     companion object {
         /**
-         * When EXTRA_CAPTURE_REGION_RESULT_RECEIVER is passed as intent extra
-         * the activity will send the [CaptureRegion] to the result receiver
-         * instead of returning media projection instance through activity result.
+         * When EXTRA_CAPTURE_REGION_RESULT_RECEIVER is passed as intent extra the activity will
+         * send the [CaptureRegion] to the result receiver instead of returning media projection
+         * instance through activity result.
          */
         const val EXTRA_CAPTURE_REGION_RESULT_RECEIVER = "capture_region_result_receiver"
         const val KEY_CAPTURE_TARGET = "capture_region"
diff --git a/packages/SystemUI/src/com/android/systemui/media/dagger/MediaProjectionModule.kt b/packages/SystemUI/src/com/android/systemui/media/dagger/MediaProjectionModule.kt
deleted file mode 100644
index 185b4fc..0000000
--- a/packages/SystemUI/src/com/android/systemui/media/dagger/MediaProjectionModule.kt
+++ /dev/null
@@ -1,89 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.media.dagger
-
-import android.app.Activity
-import android.content.ComponentName
-import android.content.Context
-import com.android.systemui.dagger.qualifiers.Application
-import com.android.systemui.media.MediaProjectionAppSelectorActivity
-import com.android.systemui.mediaprojection.appselector.MediaProjectionAppSelectorController
-import com.android.systemui.mediaprojection.appselector.data.ActivityTaskManagerThumbnailLoader
-import com.android.systemui.mediaprojection.appselector.data.AppIconLoader
-import com.android.systemui.mediaprojection.appselector.data.IconLoaderLibAppIconLoader
-import com.android.systemui.mediaprojection.appselector.data.RecentTaskListProvider
-import com.android.systemui.mediaprojection.appselector.data.RecentTaskThumbnailLoader
-import com.android.systemui.mediaprojection.appselector.data.ShellRecentTaskListProvider
-import dagger.Binds
-import dagger.Module
-import dagger.Provides
-import dagger.multibindings.ClassKey
-import dagger.multibindings.IntoMap
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.SupervisorJob
-import javax.inject.Qualifier
-
-@Qualifier
-@Retention(AnnotationRetention.BINARY)
-annotation class MediaProjectionAppSelector
-
-@Module
-abstract class MediaProjectionModule {
-
-    @Binds
-    @IntoMap
-    @ClassKey(MediaProjectionAppSelectorActivity::class)
-    abstract fun provideMediaProjectionAppSelectorActivity(
-        activity: MediaProjectionAppSelectorActivity
-    ): Activity
-
-    @Binds
-    abstract fun bindRecentTaskThumbnailLoader(
-        impl: ActivityTaskManagerThumbnailLoader
-    ): RecentTaskThumbnailLoader
-
-    @Binds
-    abstract fun bindRecentTaskListProvider(
-        impl: ShellRecentTaskListProvider
-    ): RecentTaskListProvider
-
-    @Binds
-    abstract fun bindAppIconLoader(impl: IconLoaderLibAppIconLoader): AppIconLoader
-
-    companion object {
-        @Provides
-        fun provideController(
-            recentTaskListProvider: RecentTaskListProvider,
-            context: Context,
-            @MediaProjectionAppSelector scope: CoroutineScope
-        ): MediaProjectionAppSelectorController {
-            val appSelectorComponentName =
-                ComponentName(context, MediaProjectionAppSelectorActivity::class.java)
-
-            return MediaProjectionAppSelectorController(
-                recentTaskListProvider,
-                scope,
-                appSelectorComponentName
-            )
-        }
-
-        @MediaProjectionAppSelector
-        @Provides
-        fun provideCoroutineScope(@Application applicationScope: CoroutineScope): CoroutineScope =
-            CoroutineScope(applicationScope.coroutineContext + SupervisorJob())
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorComponent.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorComponent.kt
new file mode 100644
index 0000000..7fd100f
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorComponent.kt
@@ -0,0 +1,123 @@
+/*
+ * 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.mediaprojection.appselector
+
+import android.app.Activity
+import android.content.ComponentName
+import android.content.Context
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.media.MediaProjectionAppSelectorActivity
+import com.android.systemui.mediaprojection.appselector.data.ActivityTaskManagerThumbnailLoader
+import com.android.systemui.mediaprojection.appselector.data.AppIconLoader
+import com.android.systemui.mediaprojection.appselector.data.IconLoaderLibAppIconLoader
+import com.android.systemui.mediaprojection.appselector.data.RecentTaskListProvider
+import com.android.systemui.mediaprojection.appselector.data.RecentTaskThumbnailLoader
+import com.android.systemui.mediaprojection.appselector.data.ShellRecentTaskListProvider
+import com.android.systemui.mediaprojection.appselector.view.MediaProjectionRecentsViewController
+import com.android.systemui.mediaprojection.appselector.view.TaskPreviewSizeProvider
+import com.android.systemui.statusbar.phone.ConfigurationControllerImpl
+import com.android.systemui.statusbar.policy.ConfigurationController
+import dagger.Binds
+import dagger.BindsInstance
+import dagger.Module
+import dagger.Provides
+import dagger.Subcomponent
+import dagger.multibindings.ClassKey
+import dagger.multibindings.IntoMap
+import javax.inject.Qualifier
+import javax.inject.Scope
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.SupervisorJob
+
+@Qualifier @Retention(AnnotationRetention.BINARY) annotation class MediaProjectionAppSelector
+
+@Retention(AnnotationRetention.RUNTIME) @Scope annotation class MediaProjectionAppSelectorScope
+
+@Module(subcomponents = [MediaProjectionAppSelectorComponent::class])
+interface MediaProjectionModule {
+    @Binds
+    @IntoMap
+    @ClassKey(MediaProjectionAppSelectorActivity::class)
+    fun provideMediaProjectionAppSelectorActivity(
+        activity: MediaProjectionAppSelectorActivity
+    ): Activity
+}
+
+/** Scoped values for [MediaProjectionAppSelectorComponent].
+ *  We create a scope for the activity so certain dependencies like [TaskPreviewSizeProvider]
+ *  could be reused. */
+@Module
+interface MediaProjectionAppSelectorModule {
+
+    @Binds
+    @MediaProjectionAppSelectorScope
+    fun bindRecentTaskThumbnailLoader(
+        impl: ActivityTaskManagerThumbnailLoader
+    ): RecentTaskThumbnailLoader
+
+    @Binds
+    @MediaProjectionAppSelectorScope
+    fun bindRecentTaskListProvider(impl: ShellRecentTaskListProvider): RecentTaskListProvider
+
+    @Binds
+    @MediaProjectionAppSelectorScope
+    fun bindAppIconLoader(impl: IconLoaderLibAppIconLoader): AppIconLoader
+
+    companion object {
+        @Provides
+        @MediaProjectionAppSelector
+        @MediaProjectionAppSelectorScope
+        fun provideAppSelectorComponentName(context: Context): ComponentName =
+                ComponentName(context, MediaProjectionAppSelectorActivity::class.java)
+
+        @Provides
+        @MediaProjectionAppSelector
+        @MediaProjectionAppSelectorScope
+        fun bindConfigurationController(
+            activity: MediaProjectionAppSelectorActivity
+        ): ConfigurationController = ConfigurationControllerImpl(activity)
+
+        @Provides
+        @MediaProjectionAppSelector
+        @MediaProjectionAppSelectorScope
+        fun provideCoroutineScope(@Application applicationScope: CoroutineScope): CoroutineScope =
+            CoroutineScope(applicationScope.coroutineContext + SupervisorJob())
+    }
+}
+
+@Subcomponent(modules = [MediaProjectionAppSelectorModule::class])
+@MediaProjectionAppSelectorScope
+interface MediaProjectionAppSelectorComponent {
+
+    /** Generates [MediaProjectionAppSelectorComponent]. */
+    @Subcomponent.Factory
+    interface Factory {
+        /**
+         * Create a factory to inject the activity into the graph
+         */
+        fun create(
+            @BindsInstance activity: MediaProjectionAppSelectorActivity,
+            @BindsInstance view: MediaProjectionAppSelectorView,
+            @BindsInstance resultHandler: MediaProjectionAppSelectorResultHandler,
+        ): MediaProjectionAppSelectorComponent
+    }
+
+    val controller: MediaProjectionAppSelectorController
+    val recentsViewController: MediaProjectionRecentsViewController
+
+    @MediaProjectionAppSelector val configurationController: ConfigurationController
+}
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorController.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorController.kt
index 2b381a9..d744a40b 100644
--- a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorController.kt
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorController.kt
@@ -17,20 +17,22 @@
 package com.android.systemui.mediaprojection.appselector
 
 import android.content.ComponentName
-import com.android.systemui.media.dagger.MediaProjectionAppSelector
 import com.android.systemui.mediaprojection.appselector.data.RecentTask
 import com.android.systemui.mediaprojection.appselector.data.RecentTaskListProvider
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.cancel
 import kotlinx.coroutines.launch
+import javax.inject.Inject
 
-class MediaProjectionAppSelectorController(
+@MediaProjectionAppSelectorScope
+class MediaProjectionAppSelectorController @Inject constructor(
     private val recentTaskListProvider: RecentTaskListProvider,
+    private val view: MediaProjectionAppSelectorView,
     @MediaProjectionAppSelector private val scope: CoroutineScope,
-    private val appSelectorComponentName: ComponentName
+    @MediaProjectionAppSelector private val appSelectorComponentName: ComponentName
 ) {
 
-    fun init(view: MediaProjectionAppSelectorView) {
+    fun init() {
         scope.launch {
             val tasks = recentTaskListProvider.loadRecentTasks().sortTasks()
             view.bind(tasks)
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorResultHandler.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorResultHandler.kt
new file mode 100644
index 0000000..93c3bce
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorResultHandler.kt
@@ -0,0 +1,15 @@
+package com.android.systemui.mediaprojection.appselector
+
+import android.os.IBinder
+
+/**
+ * Interface that allows to continue the media projection flow and return the selected app
+ * result to the original caller.
+ */
+interface MediaProjectionAppSelectorResultHandler {
+    /**
+     * Return selected app to the original caller of the media projection app picker.
+     * @param launchCookie launch cookie of the launched activity of the target app
+     */
+    fun returnSelectedApp(launchCookie: IBinder)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/MediaProjectionRecentsViewController.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/MediaProjectionRecentsViewController.kt
new file mode 100644
index 0000000..c816446
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/MediaProjectionRecentsViewController.kt
@@ -0,0 +1,155 @@
+/*
+ * 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.mediaprojection.appselector.view
+
+import android.app.ActivityOptions
+import android.app.IActivityTaskManager
+import android.graphics.Rect
+import android.os.Binder
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import androidx.recyclerview.widget.LinearLayoutManager
+import androidx.recyclerview.widget.RecyclerView
+import com.android.systemui.R
+import com.android.systemui.mediaprojection.appselector.MediaProjectionAppSelectorResultHandler
+import com.android.systemui.mediaprojection.appselector.MediaProjectionAppSelectorScope
+import com.android.systemui.mediaprojection.appselector.data.RecentTask
+import com.android.systemui.mediaprojection.appselector.view.RecentTasksAdapter.RecentTaskClickListener
+import com.android.systemui.mediaprojection.appselector.view.TaskPreviewSizeProvider.TaskPreviewSizeListener
+import com.android.systemui.util.recycler.HorizontalSpacerItemDecoration
+import javax.inject.Inject
+
+/**
+ * Controller that handles view of the recent apps selector in the media projection activity.
+ * It is responsible for creating and updating recent apps view.
+ */
+@MediaProjectionAppSelectorScope
+class MediaProjectionRecentsViewController
+@Inject
+constructor(
+    private val recentTasksAdapterFactory: RecentTasksAdapter.Factory,
+    private val taskViewSizeProvider: TaskPreviewSizeProvider,
+    private val activityTaskManager: IActivityTaskManager,
+    private val resultHandler: MediaProjectionAppSelectorResultHandler,
+) : RecentTaskClickListener, TaskPreviewSizeListener {
+
+    private var views: Views? = null
+    private var lastBoundData: List<RecentTask>? = null
+
+    init {
+        taskViewSizeProvider.addCallback(this)
+    }
+
+    fun createView(parent: ViewGroup): ViewGroup =
+        views?.root ?: createRecentViews(parent).also {
+            views = it
+            lastBoundData?.let { recents -> bind(recents) }
+        }.root
+
+    fun bind(recentTasks: List<RecentTask>) {
+        views?.apply {
+            if (recentTasks.isEmpty()) {
+                root.visibility = View.GONE
+                return
+            }
+
+            progress.visibility = View.GONE
+            recycler.visibility = View.VISIBLE
+            root.visibility = View.VISIBLE
+
+            recycler.adapter =
+                recentTasksAdapterFactory.create(
+                    recentTasks,
+                    this@MediaProjectionRecentsViewController
+                )
+        }
+
+        lastBoundData = recentTasks
+    }
+
+    private fun createRecentViews(parent: ViewGroup): Views {
+        val recentsRoot =
+            LayoutInflater.from(parent.context)
+                .inflate(R.layout.media_projection_recent_tasks, parent, /* attachToRoot= */ false)
+                as ViewGroup
+
+        val container = recentsRoot.findViewById<View>(R.id.media_projection_recent_tasks_container)
+        container.setTaskHeightSize()
+
+        val progress = recentsRoot.requireViewById<View>(R.id.media_projection_recent_tasks_loader)
+        val recycler =
+            recentsRoot.requireViewById<RecyclerView>(R.id.media_projection_recent_tasks_recycler)
+        recycler.layoutManager =
+            LinearLayoutManager(
+                parent.context,
+                LinearLayoutManager.HORIZONTAL,
+                /* reverseLayout= */ false
+            )
+
+        val itemDecoration =
+            HorizontalSpacerItemDecoration(
+                parent.resources.getDimensionPixelOffset(
+                    R.dimen.media_projection_app_selector_recents_padding
+                )
+            )
+        recycler.addItemDecoration(itemDecoration)
+
+        return Views(recentsRoot, container, progress, recycler)
+    }
+
+    override fun onRecentAppClicked(task: RecentTask, view: View) {
+        val launchCookie = Binder()
+        val activityOptions =
+            ActivityOptions.makeScaleUpAnimation(
+                view,
+                /* startX= */ 0,
+                /* startY= */ 0,
+                view.width,
+                view.height
+            )
+        activityOptions.launchCookie = launchCookie
+
+        activityTaskManager.startActivityFromRecents(task.taskId, activityOptions.toBundle())
+        resultHandler.returnSelectedApp(launchCookie)
+    }
+
+    override fun onTaskSizeChanged(size: Rect) {
+        views?.recentsContainer?.setTaskHeightSize()
+    }
+
+    private fun View.setTaskHeightSize() {
+        val thumbnailHeight = taskViewSizeProvider.size.height()
+        val itemHeight =
+            thumbnailHeight +
+                context.resources.getDimensionPixelSize(
+                    R.dimen.media_projection_app_selector_task_icon_size
+                ) +
+                context.resources.getDimensionPixelSize(
+                    R.dimen.media_projection_app_selector_task_icon_margin
+                ) * 2
+
+        layoutParams = layoutParams.apply { height = itemHeight }
+    }
+
+    private class Views(
+        val root: ViewGroup,
+        val recentsContainer: View,
+        val progress: View,
+        val recycler: RecyclerView
+    )
+}
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/MediaProjectionTaskView.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/MediaProjectionTaskView.kt
new file mode 100644
index 0000000..b682bd1
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/MediaProjectionTaskView.kt
@@ -0,0 +1,175 @@
+/*
+ * 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.mediaprojection.appselector.view
+
+import android.content.Context
+import android.graphics.BitmapShader
+import android.graphics.Canvas
+import android.graphics.Color
+import android.graphics.Paint
+import android.graphics.Rect
+import android.graphics.Shader
+import android.util.AttributeSet
+import android.view.View
+import android.view.WindowManager
+import androidx.core.content.getSystemService
+import androidx.core.content.res.use
+import com.android.internal.R as AndroidR
+import com.android.systemui.R
+import com.android.systemui.mediaprojection.appselector.data.RecentTask
+import com.android.systemui.shared.recents.model.ThumbnailData
+import com.android.systemui.shared.recents.utilities.PreviewPositionHelper
+import com.android.systemui.shared.recents.utilities.Utilities.isTablet
+
+/**
+ * Custom view that shows a thumbnail preview of one recent task based on [ThumbnailData].
+ * It handles proper cropping and positioning of the thumbnail using [PreviewPositionHelper].
+ */
+class MediaProjectionTaskView
+@JvmOverloads
+constructor(context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0) :
+    View(context, attrs, defStyleAttr) {
+
+    private val defaultBackgroundColor: Int
+
+    init {
+        val backgroundColorAttribute = intArrayOf(android.R.attr.colorBackgroundFloating)
+        defaultBackgroundColor =
+            context.obtainStyledAttributes(backgroundColorAttribute).use {
+                it.getColor(/* index= */ 0, /* defValue= */ Color.BLACK)
+            }
+    }
+
+    private val windowManager: WindowManager = context.getSystemService()!!
+    private val paint = Paint(Paint.ANTI_ALIAS_FLAG)
+    private val backgroundPaint =
+        Paint(Paint.ANTI_ALIAS_FLAG).apply { color = defaultBackgroundColor }
+    private val cornerRadius =
+        context.resources.getDimensionPixelSize(
+            R.dimen.media_projection_app_selector_task_rounded_corners
+        )
+    private val previewPositionHelper = PreviewPositionHelper()
+    private val previewRect = Rect()
+
+    private var task: RecentTask? = null
+    private var thumbnailData: ThumbnailData? = null
+
+    private var bitmapShader: BitmapShader? = null
+
+    fun bindTask(task: RecentTask?, thumbnailData: ThumbnailData?) {
+        this.task = task
+        this.thumbnailData = thumbnailData
+
+        // Strip alpha channel to make sure that the color is not semi-transparent
+        val color = (task?.colorBackground ?: Color.BLACK) or 0xFF000000.toInt()
+
+        paint.color = color
+        backgroundPaint.color = color
+
+        refresh()
+    }
+
+    override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
+        updateThumbnailMatrix()
+        invalidate()
+    }
+
+    override fun onDraw(canvas: Canvas) {
+        // Always draw the background since the snapshots might be translucent or partially empty
+        // (For example, tasks been reparented out of dismissing split root when drag-to-dismiss
+        // split screen).
+        canvas.drawRoundRect(
+            0f,
+            1f,
+            width.toFloat(),
+            (height - 1).toFloat(),
+            cornerRadius.toFloat(),
+            cornerRadius.toFloat(),
+            backgroundPaint
+        )
+
+        val drawBackgroundOnly = task == null || bitmapShader == null || thumbnailData == null
+        if (drawBackgroundOnly) {
+            return
+        }
+
+        // Draw the task thumbnail using bitmap shader in the paint
+        canvas.drawRoundRect(
+            0f,
+            0f,
+            width.toFloat(),
+            height.toFloat(),
+            cornerRadius.toFloat(),
+            cornerRadius.toFloat(),
+            paint
+        )
+    }
+
+    private fun refresh() {
+        val thumbnailBitmap = thumbnailData?.thumbnail
+
+        if (thumbnailBitmap != null) {
+            thumbnailBitmap.prepareToDraw()
+            bitmapShader =
+                BitmapShader(thumbnailBitmap, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP)
+            paint.shader = bitmapShader
+            updateThumbnailMatrix()
+        } else {
+            bitmapShader = null
+            paint.shader = null
+        }
+
+        invalidate()
+    }
+
+    private fun updateThumbnailMatrix() {
+        previewPositionHelper.isOrientationChanged = false
+
+        val bitmapShader = bitmapShader ?: return
+        val thumbnailData = thumbnailData ?: return
+        val display = context.display ?: return
+        val windowMetrics = windowManager.maximumWindowMetrics
+
+        previewRect.set(0, 0, thumbnailData.thumbnail.width, thumbnailData.thumbnail.height)
+
+        val currentRotation: Int = display.rotation
+        val displayWidthPx = windowMetrics.bounds.width()
+        val isRtl = layoutDirection == LAYOUT_DIRECTION_RTL
+        val isTablet = isTablet(context)
+        val taskbarSize =
+            if (isTablet) {
+                resources.getDimensionPixelSize(AndroidR.dimen.taskbar_frame_height)
+            } else {
+                0
+            }
+
+        previewPositionHelper.updateThumbnailMatrix(
+            previewRect,
+            thumbnailData,
+            measuredWidth,
+            measuredHeight,
+            displayWidthPx,
+            taskbarSize,
+            isTablet,
+            currentRotation,
+            isRtl
+        )
+
+        bitmapShader.setLocalMatrix(previewPositionHelper.matrix)
+        paint.shader = bitmapShader
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/RecentTaskViewHolder.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/RecentTaskViewHolder.kt
index ec5abc7..15cfeee 100644
--- a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/RecentTaskViewHolder.kt
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/RecentTaskViewHolder.kt
@@ -16,15 +16,17 @@
 
 package com.android.systemui.mediaprojection.appselector.view
 
+import android.graphics.Rect
 import android.view.View
 import android.view.ViewGroup
 import android.widget.ImageView
 import androidx.recyclerview.widget.RecyclerView
 import com.android.systemui.R
-import com.android.systemui.media.dagger.MediaProjectionAppSelector
+import com.android.systemui.mediaprojection.appselector.MediaProjectionAppSelector
 import com.android.systemui.mediaprojection.appselector.data.AppIconLoader
 import com.android.systemui.mediaprojection.appselector.data.RecentTask
 import com.android.systemui.mediaprojection.appselector.data.RecentTaskThumbnailLoader
+import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener
 import dagger.assisted.Assisted
 import dagger.assisted.AssistedFactory
 import dagger.assisted.AssistedInject
@@ -32,19 +34,27 @@
 import kotlinx.coroutines.Job
 import kotlinx.coroutines.launch
 
-class RecentTaskViewHolder @AssistedInject constructor(
-    @Assisted root: ViewGroup,
+class RecentTaskViewHolder
+@AssistedInject
+constructor(
+    @Assisted private val root: ViewGroup,
     private val iconLoader: AppIconLoader,
     private val thumbnailLoader: RecentTaskThumbnailLoader,
+    private val taskViewSizeProvider: TaskPreviewSizeProvider,
     @MediaProjectionAppSelector private val scope: CoroutineScope
-) : RecyclerView.ViewHolder(root) {
+) : RecyclerView.ViewHolder(root), ConfigurationListener, TaskPreviewSizeProvider.TaskPreviewSizeListener {
 
+    val thumbnailView: MediaProjectionTaskView = root.requireViewById(R.id.task_thumbnail)
     private val iconView: ImageView = root.requireViewById(R.id.task_icon)
-    private val thumbnailView: ImageView = root.requireViewById(R.id.task_thumbnail)
 
     private var job: Job? = null
 
+    init {
+        updateThumbnailSize()
+    }
+
     fun bind(task: RecentTask, onClick: (View) -> Unit) {
+        taskViewSizeProvider.addCallback(this)
         job?.cancel()
 
         job =
@@ -57,20 +67,33 @@
                 }
                 launch {
                     val thumbnail = thumbnailLoader.loadThumbnail(task.taskId)
-                    thumbnailView.setImageBitmap(thumbnail?.thumbnail)
+                    thumbnailView.bindTask(task, thumbnail)
                 }
             }
 
-        thumbnailView.setOnClickListener(onClick)
+        root.setOnClickListener(onClick)
     }
 
     fun onRecycled() {
+        taskViewSizeProvider.removeCallback(this)
         iconView.setImageDrawable(null)
-        thumbnailView.setImageBitmap(null)
+        thumbnailView.bindTask(null, null)
         job?.cancel()
         job = null
     }
 
+    override fun onTaskSizeChanged(size: Rect) {
+        updateThumbnailSize()
+    }
+
+    private fun updateThumbnailSize() {
+        thumbnailView.layoutParams =
+                thumbnailView.layoutParams.apply {
+                    width = taskViewSizeProvider.size.width()
+                    height = taskViewSizeProvider.size.height()
+                }
+    }
+
     @AssistedFactory
     fun interface Factory {
         fun create(root: ViewGroup): RecentTaskViewHolder
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/RecentTasksAdapter.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/RecentTasksAdapter.kt
index 51b4012..6af50a0 100644
--- a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/RecentTasksAdapter.kt
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/RecentTasksAdapter.kt
@@ -26,7 +26,9 @@
 import dagger.assisted.AssistedFactory
 import dagger.assisted.AssistedInject
 
-class RecentTasksAdapter @AssistedInject constructor(
+class RecentTasksAdapter
+@AssistedInject
+constructor(
     @Assisted private val items: List<RecentTask>,
     @Assisted private val listener: RecentTaskClickListener,
     private val viewHolderFactory: RecentTaskViewHolder.Factory
@@ -34,8 +36,8 @@
 
     override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecentTaskViewHolder {
         val taskItem =
-            LayoutInflater.from(parent.context)
-                .inflate(R.layout.media_projection_task_item, null) as ViewGroup
+                LayoutInflater.from(parent.context)
+                        .inflate(R.layout.media_projection_task_item, parent, false) as ViewGroup
 
         return viewHolderFactory.create(taskItem)
     }
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/TaskPreviewSizeProvider.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/TaskPreviewSizeProvider.kt
new file mode 100644
index 0000000..88d5eaa
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/TaskPreviewSizeProvider.kt
@@ -0,0 +1,95 @@
+/*
+ * 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.mediaprojection.appselector.view
+
+import android.content.Context
+import android.content.res.Configuration
+import android.graphics.Rect
+import android.view.WindowManager
+import com.android.internal.R as AndroidR
+import com.android.systemui.mediaprojection.appselector.MediaProjectionAppSelectorScope
+import com.android.systemui.mediaprojection.appselector.view.TaskPreviewSizeProvider.TaskPreviewSizeListener
+import com.android.systemui.shared.recents.utilities.Utilities.isTablet
+import com.android.systemui.statusbar.policy.CallbackController
+import com.android.systemui.statusbar.policy.ConfigurationController
+import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener
+import javax.inject.Inject
+
+@MediaProjectionAppSelectorScope
+class TaskPreviewSizeProvider
+@Inject
+constructor(
+    private val context: Context,
+    private val windowManager: WindowManager,
+    configurationController: ConfigurationController
+) : CallbackController<TaskPreviewSizeListener>, ConfigurationListener {
+
+    /** Returns the size of the task preview on the screen in pixels */
+    val size: Rect = calculateSize()
+
+    private val listeners = arrayListOf<TaskPreviewSizeListener>()
+
+    init {
+        configurationController.addCallback(this)
+    }
+
+    override fun onConfigChanged(newConfig: Configuration) {
+        val newSize = calculateSize()
+        if (newSize != size) {
+            size.set(newSize)
+            listeners.forEach { it.onTaskSizeChanged(size) }
+        }
+    }
+
+    private fun calculateSize(): Rect {
+        val windowMetrics = windowManager.maximumWindowMetrics
+        val maximumWindowHeight = windowMetrics.bounds.height()
+        val width = windowMetrics.bounds.width()
+        var height = maximumWindowHeight
+
+        val isTablet = isTablet(context)
+        if (isTablet) {
+            val taskbarSize =
+                context.resources.getDimensionPixelSize(AndroidR.dimen.taskbar_frame_height)
+            height -= taskbarSize
+        }
+
+        val previewSize = Rect(0, 0, width, height)
+        val scale = (height / maximumWindowHeight.toFloat()) / SCREEN_HEIGHT_TO_TASK_HEIGHT_RATIO
+        previewSize.scale(scale)
+
+        return previewSize
+    }
+
+    override fun addCallback(listener: TaskPreviewSizeListener) {
+        listeners += listener
+    }
+
+    override fun removeCallback(listener: TaskPreviewSizeListener) {
+        listeners -= listener
+    }
+
+    interface TaskPreviewSizeListener {
+        fun onTaskSizeChanged(size: Rect)
+    }
+}
+
+/**
+ * How many times smaller the task preview should be on the screen comparing to the height of the
+ * screen
+ */
+private const val SCREEN_HEIGHT_TO_TASK_HEIGHT_RATIO = 4f
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarController.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarController.java
index 3789cbb..029cf68 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarController.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarController.java
@@ -142,6 +142,9 @@
         boolean isOldConfigTablet = mIsTablet;
         mIsTablet = isTablet(mContext);
         boolean largeScreenChanged = mIsTablet != isOldConfigTablet;
+        if (mTaskbarDelegate.isInitialized()) {
+            mTaskbarDelegate.onConfigurationChanged(newConfig);
+        }
         // If we folded/unfolded while in 3 button, show navbar in folded state, hide in unfolded
         if (largeScreenChanged && updateNavbarForTaskbar()) {
             return;
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java b/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java
index 9e0c496..73fc21e 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java
@@ -40,7 +40,6 @@
 
 import android.app.StatusBarManager;
 import android.app.StatusBarManager.WindowVisibleState;
-import android.content.ComponentCallbacks;
 import android.content.Context;
 import android.content.res.Configuration;
 import android.graphics.Rect;
@@ -87,7 +86,7 @@
 @SysUISingleton
 public class TaskbarDelegate implements CommandQueue.Callbacks,
         OverviewProxyService.OverviewProxyListener, NavigationModeController.ModeChangedListener,
-        ComponentCallbacks, Dumpable {
+        Dumpable {
     private static final String TAG = TaskbarDelegate.class.getSimpleName();
 
     private final EdgeBackGestureHandler mEdgeBackGestureHandler;
@@ -225,7 +224,6 @@
         // Initialize component callback
         Display display = mDisplayManager.getDisplay(displayId);
         mWindowContext = mContext.createWindowContext(display, TYPE_APPLICATION, null);
-        mWindowContext.registerComponentCallbacks(this);
         mScreenPinningNotify = new ScreenPinningNotify(mWindowContext);
         // Set initial state for any listeners
         updateSysuiFlags();
@@ -233,6 +231,7 @@
         mLightBarController.setNavigationBar(mLightBarTransitionsController);
         mPipOptional.ifPresent(this::addPipExclusionBoundsChangeListener);
         mEdgeBackGestureHandler.setBackAnimation(mBackAnimation);
+        mEdgeBackGestureHandler.onConfigurationChanged(mContext.getResources().getConfiguration());
         mInitialized = true;
     }
 
@@ -247,10 +246,7 @@
         mNavBarHelper.destroy();
         mEdgeBackGestureHandler.onNavBarDetached();
         mScreenPinningNotify = null;
-        if (mWindowContext != null) {
-            mWindowContext.unregisterComponentCallbacks(this);
-            mWindowContext = null;
-        }
+        mWindowContext = null;
         mAutoHideController.setNavigationBar(null);
         mLightBarTransitionsController.destroy();
         mLightBarController.setNavigationBar(null);
@@ -267,8 +263,9 @@
     }
 
     /**
-     * Returns {@code true} if this taskBar is {@link #init(int)}. Returns {@code false} if this
-     * taskbar has not yet been {@link #init(int)} or has been {@link #destroy()}.
+     * Returns {@code true} if this taskBar is {@link #init(int)}.
+     * Returns {@code false} if this taskbar has not yet been {@link #init(int)}
+     * or has been {@link #destroy()}.
      */
     public boolean isInitialized() {
         return mInitialized;
@@ -460,15 +457,11 @@
         return mBehavior == BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE;
     }
 
-    @Override
     public void onConfigurationChanged(Configuration configuration) {
         mEdgeBackGestureHandler.onConfigurationChanged(configuration);
     }
 
     @Override
-    public void onLowMemory() {}
-
-    @Override
     public void showPinningEnterExitToast(boolean entering) {
         updateSysuiFlags();
         if (mScreenPinningNotify == null) {
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
index b26b42c..a8799c7 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
@@ -19,6 +19,7 @@
 
 import static com.android.systemui.classifier.Classifier.BACK_GESTURE;
 
+import android.annotation.NonNull;
 import android.app.ActivityManager;
 import android.content.ComponentName;
 import android.content.Context;
@@ -955,7 +956,7 @@
                 mStartingQuickstepRotation != rotation;
     }
 
-    public void onConfigurationChanged(Configuration newConfig) {
+    public void onConfigurationChanged(@NonNull Configuration newConfig) {
         if (mStartingQuickstepRotation > -1) {
             updateDisabledForQuickstep(newConfig);
         }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
index 184089f7..6517ff3 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
@@ -105,6 +105,7 @@
     private final Rect mClippingRect = new Rect();
     private ViewGroup mMediaHostView;
     private boolean mShouldMoveMediaOnExpansion = true;
+    private boolean mUsingCombinedHeaders = false;
 
     public QSPanel(Context context, AttributeSet attrs) {
         super(context, attrs);
@@ -148,6 +149,10 @@
         }
     }
 
+    void setUsingCombinedHeaders(boolean usingCombinedHeaders) {
+        mUsingCombinedHeaders = usingCombinedHeaders;
+    }
+
     protected void setHorizontalContentContainerClipping() {
         mHorizontalContentContainer.setClipChildren(true);
         mHorizontalContentContainer.setClipToPadding(false);
@@ -371,7 +376,9 @@
 
     protected void updatePadding() {
         final Resources res = mContext.getResources();
-        int paddingTop = res.getDimensionPixelSize(R.dimen.qs_panel_padding_top);
+        int paddingTop = res.getDimensionPixelSize(
+                mUsingCombinedHeaders ? R.dimen.qs_panel_padding_top_combined_headers
+                        : R.dimen.qs_panel_padding_top);
         int paddingBottom = res.getDimensionPixelSize(R.dimen.qs_panel_padding_bottom);
         setPaddingRelative(getPaddingStart(),
                 paddingTop,
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java
index 18bd6b7..f6db775 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java
@@ -17,6 +17,7 @@
 package com.android.systemui.qs;
 
 import static com.android.systemui.classifier.Classifier.QS_SWIPE_SIDE;
+import static com.android.systemui.flags.Flags.COMBINED_QS_HEADERS;
 import static com.android.systemui.media.dagger.MediaModule.QS_PANEL;
 import static com.android.systemui.qs.QSPanel.QS_SHOW_BRIGHTNESS;
 import static com.android.systemui.qs.dagger.QSFragmentModule.QS_USING_MEDIA_PLAYER;
@@ -27,6 +28,7 @@
 import com.android.internal.logging.MetricsLogger;
 import com.android.internal.logging.UiEventLogger;
 import com.android.systemui.dump.DumpManager;
+import com.android.systemui.flags.FeatureFlags;
 import com.android.systemui.media.MediaHierarchyManager;
 import com.android.systemui.media.MediaHost;
 import com.android.systemui.media.MediaHostState;
@@ -79,7 +81,8 @@
             QSLogger qsLogger, BrightnessController.Factory brightnessControllerFactory,
             BrightnessSliderController.Factory brightnessSliderFactory,
             FalsingManager falsingManager,
-            StatusBarKeyguardViewManager statusBarKeyguardViewManager) {
+            StatusBarKeyguardViewManager statusBarKeyguardViewManager,
+            FeatureFlags featureFlags) {
         super(view, qstileHost, qsCustomizerController, usingMediaPlayer, mediaHost,
                 metricsLogger, uiEventLogger, qsLogger, dumpManager);
         mTunerService = tunerService;
@@ -93,6 +96,7 @@
         mBrightnessController = brightnessControllerFactory.create(mBrightnessSliderController);
         mBrightnessMirrorHandler = new BrightnessMirrorHandler(mBrightnessController);
         mStatusBarKeyguardViewManager = statusBarKeyguardViewManager;
+        mView.setUsingCombinedHeaders(featureFlags.isEnabled(COMBINED_QS_HEADERS));
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
index 899e57d..66be00d 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
@@ -25,15 +25,6 @@
 import static android.view.WindowManagerPolicyConstants.NAV_BAR_MODE_3BUTTON;
 
 import static com.android.internal.accessibility.common.ShortcutConstants.CHOOSER_PACKAGE_NAME;
-import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_RECENT_TASKS;
-import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_SHELL_BACK_ANIMATION;
-import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_SHELL_DESKTOP_MODE;
-import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_SHELL_FLOATING_TASKS;
-import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_SHELL_ONE_HANDED;
-import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_SHELL_PIP;
-import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_SHELL_SHELL_TRANSITIONS;
-import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_SHELL_SPLIT_SCREEN;
-import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_SHELL_STARTING_WINDOW;
 import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_SUPPORTS_WINDOW_CORNERS;
 import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_SYSUI_PROXY;
 import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_UNLOCK_ANIMATION_CONTROLLER;
@@ -110,16 +101,7 @@
 import com.android.systemui.statusbar.phone.CentralSurfaces;
 import com.android.systemui.statusbar.phone.StatusBarWindowCallback;
 import com.android.systemui.statusbar.policy.CallbackController;
-import com.android.wm.shell.back.BackAnimation;
-import com.android.wm.shell.desktopmode.DesktopMode;
-import com.android.wm.shell.floating.FloatingTasks;
-import com.android.wm.shell.onehanded.OneHanded;
-import com.android.wm.shell.pip.Pip;
-import com.android.wm.shell.pip.PipAnimationController;
-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.transition.ShellTransitions;
+import com.android.wm.shell.sysui.ShellInterface;
 
 import java.io.PrintWriter;
 import java.util.ArrayList;
@@ -151,10 +133,8 @@
     private static final long MAX_BACKOFF_MILLIS = 10 * 60 * 1000;
 
     private final Context mContext;
-    private final Optional<Pip> mPipOptional;
+    private final ShellInterface mShellInterface;
     private final Lazy<Optional<CentralSurfaces>> mCentralSurfacesOptionalLazy;
-    private final Optional<SplitScreen> mSplitScreenOptional;
-    private final Optional<FloatingTasks> mFloatingTasksOptional;
     private SysUiState mSysUiState;
     private final Handler mHandler;
     private final Lazy<NavigationBarController> mNavBarControllerLazy;
@@ -164,14 +144,8 @@
     private final List<OverviewProxyListener> mConnectionCallbacks = new ArrayList<>();
     private final Intent mQuickStepIntent;
     private final ScreenshotHelper mScreenshotHelper;
-    private final Optional<OneHanded> mOneHandedOptional;
     private final CommandQueue mCommandQueue;
-    private final ShellTransitions mShellTransitions;
-    private final Optional<StartingSurface> mStartingSurface;
     private final KeyguardUnlockAnimationController mSysuiUnlockAnimationController;
-    private final Optional<RecentTasks> mRecentTasks;
-    private final Optional<BackAnimation> mBackAnimation;
-    private final Optional<DesktopMode> mDesktopModeOptional;
     private final UiEventLogger mUiEventLogger;
 
     private Region mActiveNavBarRegion;
@@ -456,36 +430,10 @@
             params.putBinder(KEY_EXTRA_SYSUI_PROXY, mSysUiProxy.asBinder());
             params.putFloat(KEY_EXTRA_WINDOW_CORNER_RADIUS, mWindowCornerRadius);
             params.putBoolean(KEY_EXTRA_SUPPORTS_WINDOW_CORNERS, mSupportsRoundedCornersOnWindows);
-
-            mPipOptional.ifPresent((pip) -> params.putBinder(
-                    KEY_EXTRA_SHELL_PIP,
-                    pip.createExternalInterface().asBinder()));
-            mSplitScreenOptional.ifPresent((splitscreen) -> params.putBinder(
-                    KEY_EXTRA_SHELL_SPLIT_SCREEN,
-                    splitscreen.createExternalInterface().asBinder()));
-            mFloatingTasksOptional.ifPresent(floatingTasks -> params.putBinder(
-                    KEY_EXTRA_SHELL_FLOATING_TASKS,
-                    floatingTasks.createExternalInterface().asBinder()));
-            mOneHandedOptional.ifPresent((onehanded) -> params.putBinder(
-                    KEY_EXTRA_SHELL_ONE_HANDED,
-                    onehanded.createExternalInterface().asBinder()));
-            params.putBinder(KEY_EXTRA_SHELL_SHELL_TRANSITIONS,
-                    mShellTransitions.createExternalInterface().asBinder());
-            mStartingSurface.ifPresent((startingwindow) -> params.putBinder(
-                    KEY_EXTRA_SHELL_STARTING_WINDOW,
-                    startingwindow.createExternalInterface().asBinder()));
-            params.putBinder(
-                    KEY_EXTRA_UNLOCK_ANIMATION_CONTROLLER,
+            params.putBinder(KEY_EXTRA_UNLOCK_ANIMATION_CONTROLLER,
                     mSysuiUnlockAnimationController.asBinder());
-            mRecentTasks.ifPresent(recentTasks -> params.putBinder(
-                    KEY_EXTRA_RECENT_TASKS,
-                    recentTasks.createExternalInterface().asBinder()));
-            mBackAnimation.ifPresent((backAnimation) -> params.putBinder(
-                    KEY_EXTRA_SHELL_BACK_ANIMATION,
-                    backAnimation.createExternalInterface().asBinder()));
-            mDesktopModeOptional.ifPresent((desktopMode -> params.putBinder(
-                    KEY_EXTRA_SHELL_DESKTOP_MODE,
-                    desktopMode.createExternalInterface().asBinder())));
+            // Add all the interfaces exposed by the shell
+            mShellInterface.createExternalInterfaces(params);
 
             try {
                 Log.d(TAG_OPS, "OverviewProxyService connected, initializing overview proxy");
@@ -559,21 +507,14 @@
 
     @SuppressWarnings("OptionalUsedAsFieldOrParameterType")
     @Inject
-    public OverviewProxyService(Context context, CommandQueue commandQueue,
+    public OverviewProxyService(Context context,
+            CommandQueue commandQueue,
+            ShellInterface shellInterface,
             Lazy<NavigationBarController> navBarControllerLazy,
             Lazy<Optional<CentralSurfaces>> centralSurfacesOptionalLazy,
             NavigationModeController navModeController,
             NotificationShadeWindowController statusBarWinController, SysUiState sysUiState,
-            Optional<Pip> pipOptional,
-            Optional<SplitScreen> splitScreenOptional,
-            Optional<FloatingTasks> floatingTasksOptional,
-            Optional<OneHanded> oneHandedOptional,
-            Optional<RecentTasks> recentTasks,
-            Optional<BackAnimation> backAnimation,
-            Optional<StartingSurface> startingSurface,
-            Optional<DesktopMode> desktopModeOptional,
             BroadcastDispatcher broadcastDispatcher,
-            ShellTransitions shellTransitions,
             ScreenLifecycle screenLifecycle,
             UiEventLogger uiEventLogger,
             KeyguardUnlockAnimationController sysuiUnlockAnimationController,
@@ -587,7 +528,7 @@
         }
 
         mContext = context;
-        mPipOptional = pipOptional;
+        mShellInterface = shellInterface;
         mCentralSurfacesOptionalLazy = centralSurfacesOptionalLazy;
         mHandler = new Handler();
         mNavBarControllerLazy = navBarControllerLazy;
@@ -602,11 +543,6 @@
                 .supportsRoundedCornersOnWindows(mContext.getResources());
         mSysUiState = sysUiState;
         mSysUiState.addCallback(this::notifySystemUiStateFlags);
-        mOneHandedOptional = oneHandedOptional;
-        mShellTransitions = shellTransitions;
-        mRecentTasks = recentTasks;
-        mBackAnimation = backAnimation;
-        mDesktopModeOptional = desktopModeOptional;
         mUiEventLogger = uiEventLogger;
 
         dumpManager.registerDumpable(getClass().getSimpleName(), this);
@@ -636,9 +572,6 @@
         });
         mCommandQueue = commandQueue;
 
-        mSplitScreenOptional = splitScreenOptional;
-        mFloatingTasksOptional = floatingTasksOptional;
-
         // Listen for user setup
         startTracking();
 
@@ -647,7 +580,6 @@
         // Connect to the service
         updateEnabledState();
         startConnectionToCurrentUser();
-        mStartingSurface = startingSurface;
         mSysuiUnlockAnimationController = sysuiUnlockAnimationController;
 
         // Listen for assistant changes
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelUnfoldAnimationController.kt b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelUnfoldAnimationController.kt
index e0cd482..ba779c6 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelUnfoldAnimationController.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelUnfoldAnimationController.kt
@@ -20,8 +20,8 @@
 import android.view.ViewGroup
 import com.android.systemui.R
 import com.android.systemui.shared.animation.UnfoldConstantTranslateAnimator
-import com.android.systemui.shared.animation.UnfoldConstantTranslateAnimator.Direction.LEFT
-import com.android.systemui.shared.animation.UnfoldConstantTranslateAnimator.Direction.RIGHT
+import com.android.systemui.shared.animation.UnfoldConstantTranslateAnimator.Direction.END
+import com.android.systemui.shared.animation.UnfoldConstantTranslateAnimator.Direction.START
 import com.android.systemui.shared.animation.UnfoldConstantTranslateAnimator.ViewIdToTranslate
 import com.android.systemui.unfold.SysUIUnfoldScope
 import com.android.systemui.unfold.util.NaturalRotationUnfoldProgressProvider
@@ -36,11 +36,11 @@
         UnfoldConstantTranslateAnimator(
             viewsIdToTranslate =
                 setOf(
-                    ViewIdToTranslate(R.id.quick_settings_panel, LEFT),
-                    ViewIdToTranslate(R.id.notification_stack_scroller, RIGHT),
-                    ViewIdToTranslate(R.id.rightLayout, RIGHT),
-                    ViewIdToTranslate(R.id.clock, LEFT),
-                    ViewIdToTranslate(R.id.date, LEFT)),
+                    ViewIdToTranslate(R.id.quick_settings_panel, START),
+                    ViewIdToTranslate(R.id.notification_stack_scroller, END),
+                    ViewIdToTranslate(R.id.rightLayout, END),
+                    ViewIdToTranslate(R.id.clock, START),
+                    ViewIdToTranslate(R.id.date, START)),
             progressProvider = progressProvider)
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
index 6138265..07ed013 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
@@ -1477,6 +1477,20 @@
         }
     }
 
+    /**
+     * Sets the alpha on the content, while leaving the background of the row itself as is.
+     *
+     * @param alpha alpha value to apply to the notification content
+     */
+    public void setContentAlpha(float alpha) {
+        for (NotificationContentView l : mLayouts) {
+            l.setAlpha(alpha);
+        }
+        if (mChildrenContainer != null) {
+            mChildrenContainer.setAlpha(alpha);
+        }
+    }
+
     public void setIsLowPriority(boolean isLowPriority) {
         mIsLowPriority = isLowPriority;
         mPrivateLayout.setIsLowPriority(isLowPriority);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java
index df81c0e..8de0365 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java
@@ -1986,6 +1986,25 @@
     public void setRemoteInputVisible(boolean remoteInputVisible) {
         mRemoteInputVisible = remoteInputVisible;
         setClipChildren(!remoteInputVisible);
+        setActionsImportanceForAccessibility(
+                remoteInputVisible ? View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS
+                        : View.IMPORTANT_FOR_ACCESSIBILITY_AUTO);
+    }
+
+    private void setActionsImportanceForAccessibility(int mode) {
+        if (mExpandedChild != null) {
+            setActionsImportanceForAccessibility(mode, mExpandedChild);
+        }
+        if (mHeadsUpChild != null) {
+            setActionsImportanceForAccessibility(mode, mHeadsUpChild);
+        }
+    }
+
+    private void setActionsImportanceForAccessibility(int mode, View child) {
+        View actionsCandidate = child.findViewById(com.android.internal.R.id.actions);
+        if (actionsCandidate != null) {
+            actionsCandidate.setImportantForAccessibility(mode);
+        }
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
index 843a9ff..5c09d61 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
@@ -56,12 +56,13 @@
 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
 import com.android.systemui.ExpandHelper;
 import com.android.systemui.Gefingerpoken;
-import com.android.systemui.R;
 import com.android.systemui.SwipeHelper;
 import com.android.systemui.classifier.Classifier;
 import com.android.systemui.classifier.FalsingCollector;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.dump.DumpManager;
+import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.flags.Flags;
 import com.android.systemui.media.KeyguardMediaController;
 import com.android.systemui.plugins.FalsingManager;
 import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin;
@@ -178,6 +179,7 @@
     @Nullable private Boolean mHistoryEnabled;
     private int mBarState;
     private HeadsUpAppearanceController mHeadsUpAppearanceController;
+    private final FeatureFlags mFeatureFlags;
 
     private View mLongPressedView;
 
@@ -639,7 +641,8 @@
             InteractionJankMonitor jankMonitor,
             StackStateLogger stackLogger,
             NotificationStackScrollLogger logger,
-            NotificationStackSizeCalculator notificationStackSizeCalculator) {
+            NotificationStackSizeCalculator notificationStackSizeCalculator,
+            FeatureFlags featureFlags) {
         mStackStateLogger = stackLogger;
         mLogger = logger;
         mAllowLongPress = allowLongPress;
@@ -675,6 +678,7 @@
         mUiEventLogger = uiEventLogger;
         mRemoteInputManager = remoteInputManager;
         mShadeController = shadeController;
+        mFeatureFlags = featureFlags;
         updateResources();
     }
 
@@ -739,9 +743,7 @@
 
         mLockscreenUserManager.addUserChangedListener(mLockscreenUserChangeListener);
 
-        mFadeNotificationsOnDismiss =  // TODO: this should probably be injected directly
-                mResources.getBoolean(R.bool.config_fadeNotificationsOnDismiss);
-
+        mFadeNotificationsOnDismiss = mFeatureFlags.isEnabled(Flags.NOTIFICATION_DISMISSAL_FADE);
         mNotificationRoundnessManager.setOnRoundingChangedCallback(mView::invalidate);
         mView.addOnExpandedHeightChangedListener(mNotificationRoundnessManager::setExpanded);
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelper.java
index 2d2fbe5..ee57411 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelper.java
@@ -185,6 +185,13 @@
         return false;
     }
 
+    @Override
+    protected void updateSwipeProgressAlpha(View animView, float alpha) {
+        if (animView instanceof ExpandableNotificationRow) {
+            ((ExpandableNotificationRow) animView).setContentAlpha(alpha);
+        }
+    }
+
     @VisibleForTesting
     protected void handleMenuRowSwipe(MotionEvent ev, View animView, float velocity,
             NotificationMenuRowPlugin menuRow) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java
index eeed070..32591ce 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java
@@ -884,6 +884,9 @@
                 childViewState.zTranslation = baseZ;
             } else {
                 float factor = (notificationEnd - shelfStart) / shelfHeight;
+                if (Float.isNaN(factor)) { // Avoid problems when the above is 0/0.
+                    factor = 1.0f;
+                }
                 factor = Math.min(factor, 1.0f);
                 childViewState.zTranslation = baseZ + factor * zDistanceBetweenElements;
             }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java
index 54d39fd..de7b152 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java
@@ -93,13 +93,13 @@
     private boolean mControlScreenOffAnimation;
     private boolean mIsQuickPickupEnabled;
 
-    private boolean mKeyguardShowing;
+    private boolean mKeyguardVisible;
     @VisibleForTesting
     final KeyguardUpdateMonitorCallback mKeyguardVisibilityCallback =
             new KeyguardUpdateMonitorCallback() {
                 @Override
-                public void onKeyguardVisibilityChanged(boolean showing) {
-                    mKeyguardShowing = showing;
+                public void onKeyguardVisibilityChanged(boolean visible) {
+                    mKeyguardVisible = visible;
                     updateControlScreenOff();
                 }
 
@@ -293,7 +293,7 @@
     public void updateControlScreenOff() {
         if (!getDisplayNeedsBlanking()) {
             final boolean controlScreenOff =
-                    getAlwaysOn() && (mKeyguardShowing || shouldControlUnlockedScreenOff());
+                    getAlwaysOn() && (mKeyguardVisible || shouldControlUnlockedScreenOff());
             setControlScreenOffAnimation(controlScreenOff);
         }
     }
@@ -348,7 +348,7 @@
     }
 
     private boolean willAnimateFromLockScreenToAod() {
-        return getAlwaysOn() && mKeyguardShowing;
+        return getAlwaysOn() && mKeyguardVisible;
     }
 
     private boolean getBoolean(String propName, int resId) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardLiftController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardLiftController.kt
index b58dbe2..e3e8572 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardLiftController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardLiftController.kt
@@ -88,7 +88,7 @@
             updateListeningState()
         }
 
-        override fun onKeyguardVisibilityChanged(showing: Boolean) {
+        override fun onKeyguardVisibilityChanged(visible: Boolean) {
             updateListeningState()
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java
index 054bd28..14cebf4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java
@@ -187,8 +187,8 @@
                 }
 
                 @Override
-                public void onKeyguardVisibilityChanged(boolean showing) {
-                    if (showing) {
+                public void onKeyguardVisibilityChanged(boolean visible) {
+                    if (visible) {
                         updateUserSwitcher();
                     }
                 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
index 4d1c361..9f93223 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
@@ -1534,7 +1534,7 @@
     private class KeyguardVisibilityCallback extends KeyguardUpdateMonitorCallback {
 
         @Override
-        public void onKeyguardVisibilityChanged(boolean showing) {
+        public void onKeyguardVisibilityChanged(boolean visible) {
             mNeedsDrawableColorUpdate = true;
             scheduleUpdate();
         }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
index a0578fa..dfb7aab 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
@@ -405,8 +405,9 @@
         } else if (mNotificationPanelViewController.isUnlockHintRunning()) {
             if (mBouncer != null) {
                 mBouncer.setExpansion(KeyguardBouncer.EXPANSION_HIDDEN);
+            } else {
+                mBouncerInteractor.setExpansion(KeyguardBouncer.EXPANSION_HIDDEN);
             }
-            mBouncerInteractor.setExpansion(KeyguardBouncer.EXPANSION_HIDDEN);
         } else if (mStatusBarStateController.getState() == StatusBarState.SHADE_LOCKED) {
             // Don't expand to the bouncer. Instead transition back to the lock screen (see
             // CentralSurfaces#showBouncerOrLockScreenIfKeyguard)
@@ -414,8 +415,9 @@
         } else if (bouncerNeedsScrimming()) {
             if (mBouncer != null) {
                 mBouncer.setExpansion(KeyguardBouncer.EXPANSION_VISIBLE);
+            } else {
+                mBouncerInteractor.setExpansion(KeyguardBouncer.EXPANSION_VISIBLE);
             }
-            mBouncerInteractor.setExpansion(KeyguardBouncer.EXPANSION_VISIBLE);
         } else if (mShowing && !hideBouncerOverDream) {
             if (!isWakeAndUnlocking()
                     && !(mBiometricUnlockController.getMode() == MODE_DISMISS_BOUNCER)
@@ -423,8 +425,9 @@
                     && !isUnlockCollapsing()) {
                 if (mBouncer != null) {
                     mBouncer.setExpansion(fraction);
+                } else {
+                    mBouncerInteractor.setExpansion(fraction);
                 }
-                mBouncerInteractor.setExpansion(fraction);
             }
             if (fraction != KeyguardBouncer.EXPANSION_HIDDEN && tracking
                     && !mKeyguardStateController.canDismissLockScreen()
@@ -432,16 +435,18 @@
                     && !bouncerIsAnimatingAway()) {
                 if (mBouncer != null) {
                     mBouncer.show(false /* resetSecuritySelection */, false /* scrimmed */);
+                } else {
+                    mBouncerInteractor.show(/* isScrimmed= */false);
                 }
-                mBouncerInteractor.show(/* isScrimmed= */false);
             }
         } else if (!mShowing && isBouncerInTransit()) {
             // Keyguard is not visible anymore, but expansion animation was still running.
             // We need to hide the bouncer, otherwise it will be stuck in transit.
             if (mBouncer != null) {
                 mBouncer.setExpansion(KeyguardBouncer.EXPANSION_HIDDEN);
+            } else {
+                mBouncerInteractor.setExpansion(KeyguardBouncer.EXPANSION_HIDDEN);
             }
-            mBouncerInteractor.setExpansion(KeyguardBouncer.EXPANSION_HIDDEN);
         } else if (mPulsing && fraction == KeyguardBouncer.EXPANSION_VISIBLE) {
             // Panel expanded while pulsing but didn't translate the bouncer (because we are
             // unlocked.) Let's simply wake-up to dismiss the lock screen.
@@ -487,8 +492,9 @@
             mCentralSurfaces.hideKeyguard();
             if (mBouncer != null) {
                 mBouncer.show(true /* resetSecuritySelection */);
+            } else {
+                mBouncerInteractor.show(true);
             }
-            mBouncerInteractor.show(true);
         } else {
             mCentralSurfaces.showKeyguard();
             if (hideBouncerWhenShowing) {
@@ -529,8 +535,9 @@
     void hideBouncer(boolean destroyView) {
         if (mBouncer != null) {
             mBouncer.hide(destroyView);
+        } else {
+            mBouncerInteractor.hide();
         }
-        mBouncerInteractor.hide();
         if (mShowing) {
             // If we were showing the bouncer and then aborting, we need to also clear out any
             // potential actions unless we actually unlocked.
@@ -551,8 +558,9 @@
         if (mShowing && !isBouncerShowing()) {
             if (mBouncer != null) {
                 mBouncer.show(false /* resetSecuritySelection */, scrimmed);
+            } else {
+                mBouncerInteractor.show(scrimmed);
             }
-            mBouncerInteractor.show(scrimmed);
         }
         updateStates();
     }
@@ -588,9 +596,10 @@
                         if (mBouncer != null) {
                             mBouncer.setDismissAction(mAfterKeyguardGoneAction,
                                     mKeyguardGoneCancelAction);
+                        } else {
+                            mBouncerInteractor.setDismissAction(mAfterKeyguardGoneAction,
+                                    mKeyguardGoneCancelAction);
                         }
-                        mBouncerInteractor.setDismissAction(mAfterKeyguardGoneAction,
-                                mKeyguardGoneCancelAction);
                         mAfterKeyguardGoneAction = null;
                         mKeyguardGoneCancelAction = null;
                     }
@@ -603,17 +612,21 @@
                 if (afterKeyguardGone) {
                     // we'll handle the dismiss action after keyguard is gone, so just show the
                     // bouncer
-                    mBouncerInteractor.show(/* isScrimmed= */true);
-                    if (mBouncer != null) mBouncer.show(false /* resetSecuritySelection */);
+                    if (mBouncer != null) {
+                        mBouncer.show(false /* resetSecuritySelection */);
+                    } else {
+                        mBouncerInteractor.show(/* isScrimmed= */true);
+                    }
                 } else {
                     // after authentication success, run dismiss action with the option to defer
                     // hiding the keyguard based on the return value of the OnDismissAction
-                    mBouncerInteractor.setDismissAction(
-                            mAfterKeyguardGoneAction, mKeyguardGoneCancelAction);
-                    mBouncerInteractor.show(/* isScrimmed= */true);
                     if (mBouncer != null) {
                         mBouncer.showWithDismissAction(mAfterKeyguardGoneAction,
                                 mKeyguardGoneCancelAction);
+                    } else {
+                        mBouncerInteractor.setDismissAction(
+                                mAfterKeyguardGoneAction, mKeyguardGoneCancelAction);
+                        mBouncerInteractor.show(/* isScrimmed= */true);
                     }
                     // bouncer will handle the dismiss action, so we no longer need to track it here
                     mAfterKeyguardGoneAction = null;
@@ -717,8 +730,9 @@
     public void onFinishedGoingToSleep() {
         if (mBouncer != null) {
             mBouncer.onScreenTurnedOff();
+        } else {
+            mBouncerInteractor.onScreenTurnedOff();
         }
-        mBouncerInteractor.onScreenTurnedOff();
     }
 
     @Override
@@ -733,11 +747,6 @@
             if (dozing || mBouncer.needsFullscreenBouncer() || mOccluded) {
                 reset(dozing /* hideBouncerWhenShowing */);
             }
-
-            if (bouncerIsOrWillBeShowing()) {
-                // Ensure bouncer is not shown when dozing state changes.
-                hideBouncer(false);
-            }
             updateStates();
 
             if (!dozing) {
@@ -835,8 +844,9 @@
         if (bouncerIsShowing()) {
             if (mBouncer != null) {
                 mBouncer.startPreHideAnimation(finishRunnable);
+            } else {
+                mBouncerInteractor.startDisappearAnimation(finishRunnable);
             }
-            mBouncerInteractor.startDisappearAnimation(finishRunnable);
             mCentralSurfaces.onBouncerPreHideAnimation();
 
             // We update the state (which will show the keyguard) only if an animation will run on
@@ -1107,13 +1117,15 @@
             if (bouncerDismissible || !showing || remoteInputActive) {
                 if (mBouncer != null) {
                     mBouncer.setBackButtonEnabled(true);
+                } else {
+                    mBouncerInteractor.setBackButtonEnabled(true);
                 }
-                mBouncerInteractor.setBackButtonEnabled(true);
             } else {
                 if (mBouncer != null) {
                     mBouncer.setBackButtonEnabled(false);
+                } else {
+                    mBouncerInteractor.setBackButtonEnabled(false);
                 }
-                mBouncerInteractor.setBackButtonEnabled(false);
             }
         }
 
@@ -1131,8 +1143,8 @@
         if (occluded != mLastOccluded || mFirstUpdate) {
             mKeyguardStateController.notifyKeyguardState(showing, occluded);
         }
-        if ((showing && !occluded) != (mLastShowing && !mLastOccluded) || mFirstUpdate) {
-            mKeyguardUpdateManager.onKeyguardVisibilityChanged(showing && !occluded);
+        if (occluded != mLastOccluded || mShowing != showing || mFirstUpdate) {
+            mKeyguardUpdateManager.setKeyguardShowing(showing, occluded);
         }
         if (bouncerIsOrWillBeShowing != mLastBouncerIsOrWillBeShowing || mFirstUpdate
                 || bouncerShowing != mLastBouncerShowing) {
@@ -1279,8 +1291,9 @@
     public void notifyKeyguardAuthenticated(boolean strongAuth) {
         if (mBouncer != null) {
             mBouncer.notifyKeyguardAuthenticated(strongAuth);
+        } else {
+            mBouncerInteractor.notifyKeyguardAuthenticated(strongAuth);
         }
-        mBouncerInteractor.notifyKeyguardAuthenticated(strongAuth);
 
         if (mAlternateAuthInterceptor != null && isShowingAlternateAuthOrAnimating()) {
             resetAlternateAuth(false);
@@ -1297,8 +1310,9 @@
         } else {
             if (mBouncer != null) {
                 mBouncer.showMessage(message, colorState);
+            } else {
+                mBouncerInteractor.showMessage(message, colorState);
             }
-            mBouncerInteractor.showMessage(message, colorState);
         }
     }
 
@@ -1345,8 +1359,9 @@
     public void updateResources() {
         if (mBouncer != null) {
             mBouncer.updateResources();
+        } else {
+            mBouncerInteractor.updateResources();
         }
-        mBouncerInteractor.updateResources();
     }
 
     public void dump(PrintWriter pw) {
@@ -1431,9 +1446,9 @@
     public void updateKeyguardPosition(float x) {
         if (mBouncer != null) {
             mBouncer.updateKeyguardPosition(x);
+        } else {
+            mBouncerInteractor.setKeyguardPosition(x);
         }
-
-        mBouncerInteractor.setKeyguardPosition(x);
     }
 
     private static class DismissWithActionRequest {
@@ -1475,9 +1490,9 @@
     public boolean isBouncerInTransit() {
         if (mBouncer != null) {
             return mBouncer.inTransit();
+        } else {
+            return mBouncerInteractor.isInTransit();
         }
-
-        return mBouncerInteractor.isInTransit();
     }
 
     /**
@@ -1486,9 +1501,9 @@
     public boolean bouncerIsShowing() {
         if (mBouncer != null) {
             return mBouncer.isShowing();
+        } else {
+            return mBouncerInteractor.isFullyShowing();
         }
-
-        return mBouncerInteractor.isFullyShowing();
     }
 
     /**
@@ -1497,9 +1512,9 @@
     public boolean bouncerIsScrimmed() {
         if (mBouncer != null) {
             return mBouncer.isScrimmed();
+        } else {
+            return mBouncerInteractor.isScrimmed();
         }
-
-        return mBouncerInteractor.isScrimmed();
     }
 
     /**
@@ -1508,9 +1523,10 @@
     public boolean bouncerIsAnimatingAway() {
         if (mBouncer != null) {
             return mBouncer.isAnimatingAway();
+        } else {
+            return mBouncerInteractor.isAnimatingAway();
         }
 
-        return mBouncerInteractor.isAnimatingAway();
     }
 
     /**
@@ -1519,9 +1535,9 @@
     public boolean bouncerWillDismissWithAction() {
         if (mBouncer != null) {
             return mBouncer.willDismissWithAction();
+        } else {
+            return mBouncerInteractor.willDismissWithAction();
         }
-
-        return mBouncerInteractor.willDismissWithAction();
     }
 
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateControllerImpl.java
index f4d08e0..437d4d4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateControllerImpl.java
@@ -435,7 +435,7 @@
         }
 
         @Override
-        public void onKeyguardVisibilityChanged(boolean showing) {
+        public void onKeyguardVisibilityChanged(boolean visible) {
             update(false /* updateAlways */);
         }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherController.java
index 712953e..494a4bb 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherController.java
@@ -91,11 +91,11 @@
     private final KeyguardUpdateMonitorCallback mInfoCallback =
             new KeyguardUpdateMonitorCallback() {
                 @Override
-                public void onKeyguardVisibilityChanged(boolean showing) {
-                    if (DEBUG) Log.d(TAG, String.format("onKeyguardVisibilityChanged %b", showing));
+                public void onKeyguardVisibilityChanged(boolean visible) {
+                    if (DEBUG) Log.d(TAG, String.format("onKeyguardVisibilityChanged %b", visible));
                     // Any time the keyguard is hidden, try to close the user switcher menu to
                     // restore keyguard to the default state
-                    if (!showing) {
+                    if (!visible) {
                         closeSwitcherIfOpenAndNotSimple(false);
                     }
                 }
diff --git a/packages/SystemUI/src/com/android/systemui/util/concurrency/SysUIConcurrencyModule.java b/packages/SystemUI/src/com/android/systemui/util/concurrency/SysUIConcurrencyModule.java
index 8c736dc..81ae6e8 100644
--- a/packages/SystemUI/src/com/android/systemui/util/concurrency/SysUIConcurrencyModule.java
+++ b/packages/SystemUI/src/com/android/systemui/util/concurrency/SysUIConcurrencyModule.java
@@ -25,6 +25,7 @@
 
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dagger.qualifiers.Background;
+import com.android.systemui.dagger.qualifiers.BroadcastRunning;
 import com.android.systemui.dagger.qualifiers.LongRunning;
 import com.android.systemui.dagger.qualifiers.Main;
 
@@ -51,6 +52,17 @@
         return thread.getLooper();
     }
 
+    /** BroadcastRunning Looper (for sending and receiving broadcasts) */
+    @Provides
+    @SysUISingleton
+    @BroadcastRunning
+    public static Looper provideBroadcastRunningLooper() {
+        HandlerThread thread = new HandlerThread("BroadcastRunning",
+                Process.THREAD_PRIORITY_BACKGROUND);
+        thread.start();
+        return thread.getLooper();
+    }
+
     /** Long running tasks Looper */
     @Provides
     @SysUISingleton
@@ -83,7 +95,17 @@
     }
 
     /**
-     * Provide a Long running Executor by default.
+     * Provide a BroadcastRunning Executor (for sending and receiving broadcasts).
+     */
+    @Provides
+    @SysUISingleton
+    @BroadcastRunning
+    public static Executor provideBroadcastRunningExecutor(@BroadcastRunning Looper looper) {
+        return new ExecutorImpl(looper);
+    }
+
+    /**
+     * Provide a Long running Executor.
      */
     @Provides
     @SysUISingleton
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
index 9aa71e9..63729e3 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
@@ -607,7 +607,7 @@
     public void testTriesToAuthenticate_whenKeyguard() {
         mKeyguardUpdateMonitor.dispatchStartedWakingUp();
         mTestableLooper.processAllMessages();
-        mKeyguardUpdateMonitor.onKeyguardVisibilityChanged(true);
+        keyguardIsVisible();
         verify(mFaceManager).authenticate(any(), any(), any(), any(), anyInt(), anyBoolean());
     }
 
@@ -617,7 +617,7 @@
         mKeyguardUpdateMonitor.dispatchStartedWakingUp();
         mTestableLooper.processAllMessages();
 
-        mKeyguardUpdateMonitor.onKeyguardVisibilityChanged(true);
+        keyguardIsVisible();
         verify(mFaceManager, never()).authenticate(any(), any(), any(), any(), anyInt(),
                 anyBoolean());
     }
@@ -630,7 +630,7 @@
 
         mKeyguardUpdateMonitor.dispatchStartedWakingUp();
         mTestableLooper.processAllMessages();
-        mKeyguardUpdateMonitor.onKeyguardVisibilityChanged(true);
+        keyguardIsVisible();
         verify(mFaceManager, never()).authenticate(any(), any(), any(), any(), anyInt(),
                 anyBoolean());
     }
@@ -654,7 +654,7 @@
 
         mKeyguardUpdateMonitor.dispatchStartedWakingUp();
         mTestableLooper.processAllMessages();
-        mKeyguardUpdateMonitor.onKeyguardVisibilityChanged(true);
+        keyguardIsVisible();
         verify(mFaceManager).authenticate(any(), any(), any(), any(), anyInt(), anyBoolean());
 
         // Stop scanning when bouncer becomes visible
@@ -668,7 +668,7 @@
 
     @Test
     public void testTriesToAuthenticate_whenAssistant() {
-        mKeyguardUpdateMonitor.setKeyguardOccluded(true);
+        mKeyguardUpdateMonitor.setKeyguardShowing(true, true);
         mKeyguardUpdateMonitor.setAssistantVisible(true);
 
         verify(mFaceManager).authenticate(any(), any(), any(), any(), anyInt(), anyBoolean());
@@ -683,7 +683,7 @@
         mKeyguardUpdateMonitor.onTrustChanged(true /* enabled */,
                 KeyguardUpdateMonitor.getCurrentUser(), 0 /* flags */,
                 new ArrayList<>());
-        mKeyguardUpdateMonitor.onKeyguardVisibilityChanged(true);
+        keyguardIsVisible();
         verify(mFaceManager).authenticate(any(), any(), any(), any(), anyInt(), anyBoolean());
     }
 
@@ -693,7 +693,7 @@
         mTestableLooper.processAllMessages();
         mKeyguardUpdateMonitor.onTrustChanged(true /* enabled */,
                 KeyguardUpdateMonitor.getCurrentUser(), 0 /* flags */, new ArrayList<>());
-        mKeyguardUpdateMonitor.onKeyguardVisibilityChanged(true);
+        keyguardIsVisible();
         verify(mFaceManager, never()).authenticate(any(), any(), any(), any(), anyInt(),
                 anyBoolean());
     }
@@ -705,7 +705,7 @@
         when(mStrongAuthTracker.getStrongAuthForUser(anyInt())).thenReturn(
                 KeyguardUpdateMonitor.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN);
 
-        mKeyguardUpdateMonitor.onKeyguardVisibilityChanged(true);
+        keyguardIsVisible();
         verify(mFaceManager, never()).authenticate(any(), any(), any(), any(), anyInt(),
                 anyBoolean());
     }
@@ -717,7 +717,7 @@
         when(mStrongAuthTracker.getStrongAuthForUser(anyInt())).thenReturn(
                 KeyguardUpdateMonitor.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_LOCKOUT);
 
-        mKeyguardUpdateMonitor.onKeyguardVisibilityChanged(true);
+        keyguardIsVisible();
         verify(mFaceManager).authenticate(any(), any(), any(), any(), anyInt(), anyBoolean());
     }
 
@@ -738,7 +738,7 @@
     public void testFaceAndFingerprintLockout_onlyFace() {
         mKeyguardUpdateMonitor.dispatchStartedWakingUp();
         mTestableLooper.processAllMessages();
-        mKeyguardUpdateMonitor.onKeyguardVisibilityChanged(true);
+        keyguardIsVisible();
 
         faceAuthLockedOut();
 
@@ -749,7 +749,7 @@
     public void testFaceAndFingerprintLockout_onlyFingerprint() {
         mKeyguardUpdateMonitor.dispatchStartedWakingUp();
         mTestableLooper.processAllMessages();
-        mKeyguardUpdateMonitor.onKeyguardVisibilityChanged(true);
+        keyguardIsVisible();
 
         mKeyguardUpdateMonitor.mFingerprintAuthenticationCallback
                 .onAuthenticationError(FINGERPRINT_ERROR_LOCKOUT_PERMANENT, "");
@@ -761,7 +761,7 @@
     public void testFaceAndFingerprintLockout() {
         mKeyguardUpdateMonitor.dispatchStartedWakingUp();
         mTestableLooper.processAllMessages();
-        mKeyguardUpdateMonitor.onKeyguardVisibilityChanged(true);
+        keyguardIsVisible();
 
         faceAuthLockedOut();
         mKeyguardUpdateMonitor.mFingerprintAuthenticationCallback
@@ -860,7 +860,7 @@
 
         mKeyguardUpdateMonitor.dispatchStartedWakingUp();
         mTestableLooper.processAllMessages();
-        mKeyguardUpdateMonitor.onKeyguardVisibilityChanged(true);
+        keyguardIsVisible();
 
         verify(mFaceManager).authenticate(any(), any(), any(), any(), anyInt(), anyBoolean());
         verify(mFingerprintManager).authenticate(any(), any(), any(), any(), anyInt(), anyInt(),
@@ -1033,8 +1033,7 @@
     public void testOccludingAppFingerprintListeningState() {
         // GIVEN keyguard isn't visible (app occluding)
         mKeyguardUpdateMonitor.dispatchStartedWakingUp();
-        mKeyguardUpdateMonitor.setKeyguardOccluded(true);
-        mKeyguardUpdateMonitor.onKeyguardVisibilityChanged(false);
+        mKeyguardUpdateMonitor.setKeyguardShowing(true, true);
         when(mStrongAuthTracker.hasUserAuthenticatedSinceBoot()).thenReturn(true);
 
         // THEN we shouldn't listen for fingerprints
@@ -1049,8 +1048,7 @@
     public void testOccludingAppRequestsFingerprint() {
         // GIVEN keyguard isn't visible (app occluding)
         mKeyguardUpdateMonitor.dispatchStartedWakingUp();
-        mKeyguardUpdateMonitor.setKeyguardOccluded(true);
-        mKeyguardUpdateMonitor.onKeyguardVisibilityChanged(false);
+        mKeyguardUpdateMonitor.setKeyguardShowing(true, true);
 
         // WHEN an occluding app requests fp
         mKeyguardUpdateMonitor.requestFingerprintAuthOnOccludingApp(true);
@@ -1142,7 +1140,7 @@
         setKeyguardBouncerVisibility(false /* isVisible */);
         mKeyguardUpdateMonitor.dispatchStartedWakingUp();
         when(mKeyguardBypassController.canBypass()).thenReturn(true);
-        mKeyguardUpdateMonitor.onKeyguardVisibilityChanged(true);
+        keyguardIsVisible();
 
         // WHEN status bar state reports a change to the keyguard that would normally indicate to
         // start running face auth
@@ -1153,8 +1151,9 @@
         // listening state to update
         assertThat(mKeyguardUpdateMonitor.isFaceDetectionRunning()).isEqualTo(false);
 
-        // WHEN biometric listening state is updated
-        mKeyguardUpdateMonitor.onKeyguardVisibilityChanged(true);
+        // WHEN biometric listening state is updated when showing state changes from false => true
+        mKeyguardUpdateMonitor.setKeyguardShowing(false, false);
+        mKeyguardUpdateMonitor.setKeyguardShowing(true, false);
 
         // THEN face unlock is running
         assertThat(mKeyguardUpdateMonitor.isFaceDetectionRunning()).isEqualTo(true);
@@ -1520,7 +1519,7 @@
     public void testFingerprintCanAuth_whenCancellationNotReceivedAndAuthFailed() {
         mKeyguardUpdateMonitor.dispatchStartedWakingUp();
         mTestableLooper.processAllMessages();
-        mKeyguardUpdateMonitor.onKeyguardVisibilityChanged(true);
+        keyguardIsVisible();
 
         verify(mFaceManager).authenticate(any(), any(), any(), any(), anyInt(), anyBoolean());
         verify(mFingerprintManager).authenticate(any(), any(), any(), any(), anyInt(), anyInt(),
@@ -1529,7 +1528,7 @@
         mKeyguardUpdateMonitor.onFaceAuthenticated(0, false);
         // Make sure keyguard is going away after face auth attempt, and that it calls
         // updateBiometricStateListeningState.
-        mKeyguardUpdateMonitor.onKeyguardVisibilityChanged(false);
+        mKeyguardUpdateMonitor.setKeyguardShowing(false, false);
         mTestableLooper.processAllMessages();
 
         verify(mHandler).postDelayed(mKeyguardUpdateMonitor.mFpCancelNotReceived,
@@ -1589,7 +1588,7 @@
     }
 
     private void keyguardIsVisible() {
-        mKeyguardUpdateMonitor.onKeyguardVisibilityChanged(true);
+        mKeyguardUpdateMonitor.setKeyguardShowing(true, false);
     }
 
     private void triggerAuthInterrupt() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthRippleControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthRippleControllerTest.kt
index 37bb0c2..0b528a5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthRippleControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthRippleControllerTest.kt
@@ -117,13 +117,12 @@
     }
 
     @Test
-    fun testFingerprintTrigger_KeyguardVisible_Ripple() {
-        // GIVEN fp exists, keyguard is visible, user doesn't need strong auth
+    fun testFingerprintTrigger_KeyguardShowing_Ripple() {
+        // GIVEN fp exists, keyguard is showing, user doesn't need strong auth
         val fpsLocation = Point(5, 5)
         `when`(authController.fingerprintSensorLocation).thenReturn(fpsLocation)
         controller.onViewAttached()
-        `when`(keyguardUpdateMonitor.isKeyguardVisible).thenReturn(true)
-        `when`(keyguardUpdateMonitor.isDreaming).thenReturn(false)
+        `when`(keyguardStateController.isShowing).thenReturn(true)
         `when`(keyguardUpdateMonitor.userNeedsStrongAuth()).thenReturn(false)
 
         // WHEN fingerprint authenticated
@@ -140,39 +139,15 @@
     }
 
     @Test
-    fun testFingerprintTrigger_Dreaming_Ripple() {
-        // GIVEN fp exists, keyguard is visible, user doesn't need strong auth
-        val fpsLocation = Point(5, 5)
-        `when`(authController.fingerprintSensorLocation).thenReturn(fpsLocation)
-        controller.onViewAttached()
-        `when`(keyguardUpdateMonitor.isKeyguardVisible).thenReturn(false)
-        `when`(keyguardUpdateMonitor.isDreaming).thenReturn(true)
-        `when`(keyguardUpdateMonitor.userNeedsStrongAuth()).thenReturn(false)
-
-        // WHEN fingerprint authenticated
-        val captor = ArgumentCaptor.forClass(KeyguardUpdateMonitorCallback::class.java)
-        verify(keyguardUpdateMonitor).registerCallback(captor.capture())
-        captor.value.onBiometricAuthenticated(
-                0 /* userId */,
-                BiometricSourceType.FINGERPRINT /* type */,
-                false /* isStrongBiometric */)
-
-        // THEN update sensor location and show ripple
-        verify(rippleView).setFingerprintSensorLocation(fpsLocation, 0f)
-        verify(rippleView).startUnlockedRipple(any())
-    }
-
-    @Test
-    fun testFingerprintTrigger_KeyguardNotVisible_NotDreaming_NoRipple() {
+    fun testFingerprintTrigger_KeyguardNotShowing_NoRipple() {
         // GIVEN fp exists & user doesn't need strong auth
         val fpsLocation = Point(5, 5)
         `when`(authController.udfpsLocation).thenReturn(fpsLocation)
         controller.onViewAttached()
         `when`(keyguardUpdateMonitor.userNeedsStrongAuth()).thenReturn(false)
 
-        // WHEN keyguard is NOT visible & fingerprint authenticated
-        `when`(keyguardUpdateMonitor.isKeyguardVisible).thenReturn(false)
-        `when`(keyguardUpdateMonitor.isDreaming).thenReturn(false)
+        // WHEN keyguard is NOT showing & fingerprint authenticated
+        `when`(keyguardStateController.isShowing).thenReturn(false)
         val captor = ArgumentCaptor.forClass(KeyguardUpdateMonitorCallback::class.java)
         verify(keyguardUpdateMonitor).registerCallback(captor.capture())
         captor.value.onBiometricAuthenticated(
@@ -186,11 +161,11 @@
 
     @Test
     fun testFingerprintTrigger_StrongAuthRequired_NoRipple() {
-        // GIVEN fp exists & keyguard is visible
+        // GIVEN fp exists & keyguard is showing
         val fpsLocation = Point(5, 5)
         `when`(authController.udfpsLocation).thenReturn(fpsLocation)
         controller.onViewAttached()
-        `when`(keyguardUpdateMonitor.isKeyguardVisible).thenReturn(true)
+        `when`(keyguardStateController.isShowing).thenReturn(true)
 
         // WHEN user needs strong auth & fingerprint authenticated
         `when`(keyguardUpdateMonitor.userNeedsStrongAuth()).thenReturn(true)
@@ -207,12 +182,12 @@
 
     @Test
     fun testFaceTriggerBypassEnabled_Ripple() {
-        // GIVEN face auth sensor exists, keyguard is visible & strong auth isn't required
+        // GIVEN face auth sensor exists, keyguard is showing & strong auth isn't required
         val faceLocation = Point(5, 5)
         `when`(authController.faceSensorLocation).thenReturn(faceLocation)
         controller.onViewAttached()
 
-        `when`(keyguardUpdateMonitor.isKeyguardVisible).thenReturn(true)
+        `when`(keyguardStateController.isShowing).thenReturn(true)
         `when`(keyguardUpdateMonitor.userNeedsStrongAuth()).thenReturn(false)
 
         // WHEN bypass is enabled & face authenticated
@@ -299,7 +274,7 @@
         val fpsLocation = Point(5, 5)
         `when`(authController.fingerprintSensorLocation).thenReturn(fpsLocation)
         controller.onViewAttached()
-        `when`(keyguardUpdateMonitor.isKeyguardVisible).thenReturn(true)
+        `when`(keyguardStateController.isShowing).thenReturn(true)
         `when`(biometricUnlockController.isWakeAndUnlock).thenReturn(true)
 
         controller.showUnlockRipple(BiometricSourceType.FINGERPRINT)
@@ -317,7 +292,7 @@
         val faceLocation = Point(5, 5)
         `when`(authController.faceSensorLocation).thenReturn(faceLocation)
         controller.onViewAttached()
-        `when`(keyguardUpdateMonitor.isKeyguardVisible).thenReturn(true)
+        `when`(keyguardStateController.isShowing).thenReturn(true)
         `when`(biometricUnlockController.isWakeAndUnlock).thenReturn(true)
         `when`(authController.isUdfpsFingerDown).thenReturn(true)
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/broadcast/BroadcastDispatcherTest.kt b/packages/SystemUI/tests/src/com/android/systemui/broadcast/BroadcastDispatcherTest.kt
index 434cb48..25bc91f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/broadcast/BroadcastDispatcherTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/broadcast/BroadcastDispatcherTest.kt
@@ -96,7 +96,7 @@
     @Mock
     private lateinit var removalPendingStore: PendingRemovalStore
 
-    private lateinit var executor: Executor
+    private lateinit var mainExecutor: Executor
 
     @Captor
     private lateinit var argumentCaptor: ArgumentCaptor<ReceiverData>
@@ -108,11 +108,12 @@
     fun setUp() {
         MockitoAnnotations.initMocks(this)
         testableLooper = TestableLooper.get(this)
-        executor = FakeExecutor(FakeSystemClock())
-        `when`(mockContext.mainExecutor).thenReturn(executor)
+        mainExecutor = FakeExecutor(FakeSystemClock())
+        `when`(mockContext.mainExecutor).thenReturn(mainExecutor)
 
         broadcastDispatcher = TestBroadcastDispatcher(
                 mockContext,
+                mainExecutor,
                 testableLooper.looper,
                 mock(Executor::class.java),
                 mock(DumpManager::class.java),
@@ -148,9 +149,9 @@
 
     @Test
     fun testAddingReceiverToCorrectUBR_executor() {
-        broadcastDispatcher.registerReceiver(broadcastReceiver, intentFilter, executor, user0)
+        broadcastDispatcher.registerReceiver(broadcastReceiver, intentFilter, mainExecutor, user0)
         broadcastDispatcher.registerReceiver(
-                broadcastReceiverOther, intentFilterOther, executor, user1)
+                broadcastReceiverOther, intentFilterOther, mainExecutor, user1)
 
         testableLooper.processAllMessages()
 
@@ -427,8 +428,9 @@
 
     private class TestBroadcastDispatcher(
         context: Context,
-        bgLooper: Looper,
-        executor: Executor,
+        mainExecutor: Executor,
+        backgroundRunningLooper: Looper,
+        backgroundRunningExecutor: Executor,
         dumpManager: DumpManager,
         logger: BroadcastDispatcherLogger,
         userTracker: UserTracker,
@@ -436,8 +438,9 @@
         var mockUBRMap: Map<Int, UserBroadcastDispatcher>
     ) : BroadcastDispatcher(
         context,
-        bgLooper,
-        executor,
+        mainExecutor,
+        backgroundRunningLooper,
+        backgroundRunningExecutor,
         dumpManager,
         logger,
         userTracker,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/ComplicationLayoutEngineTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/ComplicationLayoutEngineTest.java
index 2448f1a..849ac5e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/ComplicationLayoutEngineTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/ComplicationLayoutEngineTest.java
@@ -297,10 +297,10 @@
     }
 
     /**
-     * Ensures margin is applied
+     * Ensures default margin is applied
      */
     @Test
-    public void testMargin() {
+    public void testDefaultMargin() {
         final int margin = 5;
         final ComplicationLayoutEngine engine =
                 new ComplicationLayoutEngine(mLayout, margin, mTouchSession, 0, 0);
@@ -373,6 +373,74 @@
     }
 
     /**
+     * Ensures complication margin is applied
+     */
+    @Test
+    public void testComplicationMargin() {
+        final int defaultMargin = 5;
+        final int complicationMargin = 10;
+        final ComplicationLayoutEngine engine =
+                new ComplicationLayoutEngine(mLayout, defaultMargin, mTouchSession, 0, 0);
+
+        final ViewInfo firstViewInfo = new ViewInfo(
+                new ComplicationLayoutParams(
+                        100,
+                        100,
+                        ComplicationLayoutParams.POSITION_TOP
+                                | ComplicationLayoutParams.POSITION_END,
+                        ComplicationLayoutParams.DIRECTION_DOWN,
+                        0,
+                        complicationMargin),
+                Complication.CATEGORY_STANDARD,
+                mLayout);
+
+        addComplication(engine, firstViewInfo);
+
+        final ViewInfo secondViewInfo = new ViewInfo(
+                new ComplicationLayoutParams(
+                        100,
+                        100,
+                        ComplicationLayoutParams.POSITION_TOP
+                                | ComplicationLayoutParams.POSITION_END,
+                        ComplicationLayoutParams.DIRECTION_START,
+                        0),
+                Complication.CATEGORY_SYSTEM,
+                mLayout);
+
+        addComplication(engine, secondViewInfo);
+
+        firstViewInfo.clearInvocations();
+        secondViewInfo.clearInvocations();
+
+        final ViewInfo thirdViewInfo = new ViewInfo(
+                new ComplicationLayoutParams(
+                        100,
+                        100,
+                        ComplicationLayoutParams.POSITION_TOP
+                                | ComplicationLayoutParams.POSITION_END,
+                        ComplicationLayoutParams.DIRECTION_START,
+                        1),
+                Complication.CATEGORY_SYSTEM,
+                mLayout);
+
+        addComplication(engine, thirdViewInfo);
+
+        // The first added view should now be underneath the second view.
+        verifyChange(firstViewInfo, false, lp -> {
+            assertThat(lp.topToBottom == thirdViewInfo.view.getId()).isTrue();
+            assertThat(lp.endToEnd == ConstraintLayout.LayoutParams.PARENT_ID).isTrue();
+            assertThat(lp.topMargin).isEqualTo(complicationMargin);
+        });
+
+        // The second view should be in underneath the third view.
+        verifyChange(secondViewInfo, false, lp -> {
+            assertThat(lp.endToStart == thirdViewInfo.view.getId()).isTrue();
+            assertThat(lp.topToTop == ConstraintLayout.LayoutParams.PARENT_ID).isTrue();
+            assertThat(lp.getMarginEnd()).isEqualTo(defaultMargin);
+        });
+    }
+
+    /**
      * Ensures layout in a particular position updates.
      */
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/ComplicationLayoutParamsTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/ComplicationLayoutParamsTest.java
index 967b30d..cb7e47b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/ComplicationLayoutParamsTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/ComplicationLayoutParamsTest.java
@@ -97,6 +97,35 @@
     }
 
     /**
+     * Ensures unspecified margin uses default.
+     */
+    @Test
+    public void testUnspecifiedMarginUsesDefault() {
+        final ComplicationLayoutParams params = new ComplicationLayoutParams(
+                100,
+                100,
+                ComplicationLayoutParams.POSITION_TOP,
+                ComplicationLayoutParams.DIRECTION_DOWN,
+                3);
+        assertThat(params.getMargin(10) == 10).isTrue();
+    }
+
+    /**
+     * Ensures specified margin is used instead of default.
+     */
+    @Test
+    public void testSpecifiedMargin() {
+        final ComplicationLayoutParams params = new ComplicationLayoutParams(
+                100,
+                100,
+                ComplicationLayoutParams.POSITION_TOP,
+                ComplicationLayoutParams.DIRECTION_DOWN,
+                3,
+                10);
+        assertThat(params.getMargin(5) == 10).isTrue();
+    }
+
+    /**
      * Ensures ComplicationLayoutParams is properly duplicated on copy construction.
      */
     @Test
@@ -106,12 +135,36 @@
                 100,
                 ComplicationLayoutParams.POSITION_TOP,
                 ComplicationLayoutParams.DIRECTION_DOWN,
+                3,
+                10);
+        final ComplicationLayoutParams copy = new ComplicationLayoutParams(params);
+
+        assertThat(copy.getDirection() == params.getDirection()).isTrue();
+        assertThat(copy.getPosition() == params.getPosition()).isTrue();
+        assertThat(copy.getWeight() == params.getWeight()).isTrue();
+        assertThat(copy.getMargin(0) == params.getMargin(1)).isTrue();
+        assertThat(copy.height == params.height).isTrue();
+        assertThat(copy.width == params.width).isTrue();
+    }
+
+    /**
+     * Ensures ComplicationLayoutParams is properly duplicated on copy construction with unspecified
+     * margin.
+     */
+    @Test
+    public void testCopyConstructionWithUnspecifiedMargin() {
+        final ComplicationLayoutParams params = new ComplicationLayoutParams(
+                100,
+                100,
+                ComplicationLayoutParams.POSITION_TOP,
+                ComplicationLayoutParams.DIRECTION_DOWN,
                 3);
         final ComplicationLayoutParams copy = new ComplicationLayoutParams(params);
 
         assertThat(copy.getDirection() == params.getDirection()).isTrue();
         assertThat(copy.getPosition() == params.getPosition()).isTrue();
         assertThat(copy.getWeight() == params.getWeight()).isTrue();
+        assertThat(copy.getMargin(1) == params.getMargin(1)).isTrue();
         assertThat(copy.height == params.height).isTrue();
         assertThat(copy.width == params.width).isTrue();
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorControllerTest.kt
index 00b1f32..19d2d33 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorControllerTest.kt
@@ -25,6 +25,7 @@
 
     private val controller = MediaProjectionAppSelectorController(
         taskListProvider,
+        view,
         scope,
         appSelectorComponentName
     )
@@ -33,7 +34,7 @@
     fun initNoRecentTasks_bindsEmptyList() {
         taskListProvider.tasks = emptyList()
 
-        controller.init(view)
+        controller.init()
 
         verify(view).bind(emptyList())
     }
@@ -44,7 +45,7 @@
             createRecentTask(taskId = 1)
         )
 
-        controller.init(view)
+        controller.init()
 
         verify(view).bind(
             listOf(
@@ -62,7 +63,7 @@
         )
         taskListProvider.tasks = tasks
 
-        controller.init(view)
+        controller.init()
 
         verify(view).bind(
             listOf(
@@ -84,7 +85,7 @@
         )
         taskListProvider.tasks = tasks
 
-        controller.init(view)
+        controller.init()
 
         verify(view).bind(
             listOf(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/view/TaskPreviewSizeProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/view/TaskPreviewSizeProviderTest.kt
new file mode 100644
index 0000000..464acb6
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/view/TaskPreviewSizeProviderTest.kt
@@ -0,0 +1,134 @@
+/*
+ * 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.mediaprojection.appselector.view
+
+import android.content.Context
+import android.content.res.Configuration
+import android.content.res.Resources
+import android.graphics.Rect
+import android.util.DisplayMetrics.DENSITY_DEFAULT
+import android.view.WindowManager
+import android.view.WindowMetrics
+import androidx.test.filters.SmallTest
+import com.android.internal.R
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.mediaprojection.appselector.view.TaskPreviewSizeProvider.TaskPreviewSizeListener
+import com.android.systemui.statusbar.policy.FakeConfigurationController
+import com.android.systemui.util.mockito.eq
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.whenever
+import com.google.common.truth.Truth.assertThat
+import kotlin.math.min
+import org.junit.Before
+import org.junit.Test
+
+@SmallTest
+class TaskPreviewSizeProviderTest : SysuiTestCase() {
+
+    private val mockContext: Context = mock()
+    private val resources: Resources = mock()
+    private val windowManager: WindowManager = mock()
+    private val sizeUpdates = arrayListOf<Rect>()
+    private val testConfigurationController = FakeConfigurationController()
+
+    @Before
+    fun setup() {
+        whenever(mockContext.getSystemService(eq(WindowManager::class.java)))
+            .thenReturn(windowManager)
+        whenever(mockContext.resources).thenReturn(resources)
+    }
+
+    @Test
+    fun size_phoneDisplay_thumbnailSizeIsSmallerAndProportionalToTheScreenSize() {
+        givenDisplay(width = 400, height = 600, isTablet = false)
+
+        val size = createSizeProvider().size
+
+        assertThat(size).isEqualTo(Rect(0, 0, 100, 150))
+    }
+
+    @Test
+    fun size_tabletDisplay_thumbnailSizeProportionalToTheScreenSizeExcludingTaskbar() {
+        givenDisplay(width = 400, height = 600, isTablet = true)
+        givenTaskbarSize(20)
+
+        val size = createSizeProvider().size
+
+        assertThat(size).isEqualTo(Rect(0, 0, 97, 140))
+    }
+
+    @Test
+    fun size_phoneDisplayAndRotate_emitsSizeUpdate() {
+        givenDisplay(width = 400, height = 600, isTablet = false)
+        createSizeProvider()
+
+        givenDisplay(width = 600, height = 400, isTablet = false)
+        testConfigurationController.onConfigurationChanged(Configuration())
+
+        assertThat(sizeUpdates).containsExactly(Rect(0, 0, 150, 100))
+    }
+
+    @Test
+    fun size_phoneDisplayAndRotateConfigurationChange_returnsUpdatedSize() {
+        givenDisplay(width = 400, height = 600, isTablet = false)
+        val sizeProvider = createSizeProvider()
+
+        givenDisplay(width = 600, height = 400, isTablet = false)
+        testConfigurationController.onConfigurationChanged(Configuration())
+
+        assertThat(sizeProvider.size).isEqualTo(Rect(0, 0, 150, 100))
+    }
+
+    private fun givenTaskbarSize(size: Int) {
+        whenever(resources.getDimensionPixelSize(eq(R.dimen.taskbar_frame_height))).thenReturn(size)
+    }
+
+    private fun givenDisplay(width: Int, height: Int, isTablet: Boolean = false) {
+        val bounds = Rect(0, 0, width, height)
+        val windowMetrics = WindowMetrics(bounds, null)
+        whenever(windowManager.maximumWindowMetrics).thenReturn(windowMetrics)
+        whenever(windowManager.currentWindowMetrics).thenReturn(windowMetrics)
+
+        val minDimension = min(width, height)
+
+        // Calculate DPI so the smallest width is either considered as tablet or as phone
+        val targetSmallestWidthDpi =
+            if (isTablet) SMALLEST_WIDTH_DPI_TABLET else SMALLEST_WIDTH_DPI_PHONE
+        val densityDpi = minDimension * DENSITY_DEFAULT / targetSmallestWidthDpi
+
+        val configuration = Configuration(context.resources.configuration)
+        configuration.densityDpi = densityDpi
+        whenever(resources.configuration).thenReturn(configuration)
+    }
+
+    private fun createSizeProvider(): TaskPreviewSizeProvider {
+        val listener =
+            object : TaskPreviewSizeListener {
+                override fun onTaskSizeChanged(size: Rect) {
+                    sizeUpdates.add(size)
+                }
+            }
+
+        return TaskPreviewSizeProvider(mockContext, windowManager, testConfigurationController)
+            .also { it.addCallback(listener) }
+    }
+
+    private companion object {
+        private const val SMALLEST_WIDTH_DPI_TABLET = 800
+        private const val SMALLEST_WIDTH_DPI_PHONE = 400
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerTest.kt
index 5eb9a98..e539705 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerTest.kt
@@ -6,6 +6,7 @@
 import com.android.internal.logging.UiEventLogger
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.dump.DumpManager
+import com.android.systemui.flags.FeatureFlags
 import com.android.systemui.media.MediaHost
 import com.android.systemui.media.MediaHostState
 import com.android.systemui.plugins.FalsingManager
@@ -52,6 +53,7 @@
     @Mock private lateinit var tile: QSTile
     @Mock private lateinit var otherTile: QSTile
     @Mock private lateinit var statusBarKeyguardViewManager: StatusBarKeyguardViewManager
+    @Mock private lateinit var featureFlags: FeatureFlags
 
     private lateinit var controller: QSPanelController
 
@@ -82,7 +84,8 @@
             brightnessControllerFactory,
             brightnessSliderFactory,
             falsingManager,
-            statusBarKeyguardViewManager
+            statusBarKeyguardViewManager,
+            featureFlags
         )
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelTest.kt
index 2db58be..7c930b1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelTest.kt
@@ -159,6 +159,32 @@
     }
 
     @Test
+    fun testTopPadding_notCombinedHeaders() {
+        qsPanel.setUsingCombinedHeaders(false)
+        val padding = 10
+        val paddingCombined = 100
+        context.orCreateTestableResources.addOverride(R.dimen.qs_panel_padding_top, padding)
+        context.orCreateTestableResources.addOverride(
+                R.dimen.qs_panel_padding_top_combined_headers, paddingCombined)
+
+        qsPanel.updatePadding()
+        assertThat(qsPanel.paddingTop).isEqualTo(padding)
+    }
+
+    @Test
+    fun testTopPadding_combinedHeaders() {
+        qsPanel.setUsingCombinedHeaders(true)
+        val padding = 10
+        val paddingCombined = 100
+        context.orCreateTestableResources.addOverride(R.dimen.qs_panel_padding_top, padding)
+        context.orCreateTestableResources.addOverride(
+                R.dimen.qs_panel_padding_top_combined_headers, paddingCombined)
+
+        qsPanel.updatePadding()
+        assertThat(qsPanel.paddingTop).isEqualTo(paddingCombined)
+    }
+
+    @Test
     fun testSetSquishinessFraction_noCrash() {
         qsPanel.addView(qsPanel.mTileLayout as View, 0)
         qsPanel.addView(FrameLayout(context))
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shared/animation/UnfoldConstantTranslateAnimatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shared/animation/UnfoldConstantTranslateAnimatorTest.kt
index a4a89a4..7a74b12 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shared/animation/UnfoldConstantTranslateAnimatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shared/animation/UnfoldConstantTranslateAnimatorTest.kt
@@ -27,8 +27,8 @@
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.mockito.Mock
-import org.mockito.MockitoAnnotations
 import org.mockito.Mockito.`when` as whenever
+import org.mockito.MockitoAnnotations
 
 @SmallTest
 @RunWith(AndroidTestingRunner::class)
@@ -42,8 +42,8 @@
 
     private val viewsIdToRegister =
         setOf(
-            ViewIdToTranslate(LEFT_VIEW_ID, Direction.LEFT),
-            ViewIdToTranslate(RIGHT_VIEW_ID, Direction.RIGHT))
+            ViewIdToTranslate(START_VIEW_ID, Direction.START),
+            ViewIdToTranslate(END_VIEW_ID, Direction.END))
 
     @Before
     fun setup() {
@@ -66,41 +66,62 @@
     }
 
     @Test
-    fun onTransition_oneMovesLeft() {
+    fun onTransition_oneMovesStartWithLTR() {
         // GIVEN one view with a matching id
         val view = View(context)
-        whenever(parent.findViewById<View>(LEFT_VIEW_ID)).thenReturn(view)
+        whenever(parent.findViewById<View>(START_VIEW_ID)).thenReturn(view)
 
-        moveAndValidate(listOf(view to LEFT))
+        moveAndValidate(listOf(view to START), View.LAYOUT_DIRECTION_LTR)
     }
 
     @Test
-    fun onTransition_oneMovesLeftAndOneMovesRightMultipleTimes() {
+    fun onTransition_oneMovesStartWithRTL() {
+        // GIVEN one view with a matching id
+        val view = View(context)
+        whenever(parent.findViewById<View>(START_VIEW_ID)).thenReturn(view)
+
+        whenever(parent.getLayoutDirection()).thenReturn(View.LAYOUT_DIRECTION_RTL)
+        moveAndValidate(listOf(view to START), View.LAYOUT_DIRECTION_RTL)
+    }
+
+    @Test
+    fun onTransition_oneMovesStartAndOneMovesEndMultipleTimes() {
         // GIVEN two views with a matching id
         val leftView = View(context)
         val rightView = View(context)
-        whenever(parent.findViewById<View>(LEFT_VIEW_ID)).thenReturn(leftView)
-        whenever(parent.findViewById<View>(RIGHT_VIEW_ID)).thenReturn(rightView)
+        whenever(parent.findViewById<View>(START_VIEW_ID)).thenReturn(leftView)
+        whenever(parent.findViewById<View>(END_VIEW_ID)).thenReturn(rightView)
 
-        moveAndValidate(listOf(leftView to LEFT, rightView to RIGHT))
-        moveAndValidate(listOf(leftView to LEFT, rightView to RIGHT))
+        moveAndValidate(listOf(leftView to START, rightView to END), View.LAYOUT_DIRECTION_LTR)
+        moveAndValidate(listOf(leftView to START, rightView to END), View.LAYOUT_DIRECTION_LTR)
     }
 
-    private fun moveAndValidate(list: List<Pair<View, Int>>) {
+    private fun moveAndValidate(list: List<Pair<View, Int>>, layoutDirection: Int) {
         // Compare values as ints because -0f != 0f
 
         // WHEN the transition starts
         progressProvider.onTransitionStarted()
         progressProvider.onTransitionProgress(0f)
 
+        val rtlMultiplier = if (layoutDirection == View.LAYOUT_DIRECTION_LTR) {
+            1
+        } else {
+            -1
+        }
         list.forEach { (view, direction) ->
-            assertEquals((-MAX_TRANSLATION * direction).toInt(), view.translationX.toInt())
+            assertEquals(
+                (-MAX_TRANSLATION * direction * rtlMultiplier).toInt(),
+                view.translationX.toInt()
+            )
         }
 
         // WHEN the transition progresses, translation is updated
         progressProvider.onTransitionProgress(.5f)
         list.forEach { (view, direction) ->
-            assertEquals((-MAX_TRANSLATION / 2f * direction).toInt(), view.translationX.toInt())
+            assertEquals(
+                (-MAX_TRANSLATION / 2f * direction * rtlMultiplier).toInt(),
+                view.translationX.toInt()
+            )
         }
 
         // WHEN the transition ends, translation is completed
@@ -110,12 +131,12 @@
     }
 
     companion object {
-        private val LEFT = Direction.LEFT.multiplier.toInt()
-        private val RIGHT = Direction.RIGHT.multiplier.toInt()
+        private val START = Direction.START.multiplier.toInt()
+        private val END = Direction.END.multiplier.toInt()
 
         private const val MAX_TRANSLATION = 42f
 
-        private const val LEFT_VIEW_ID = 1
-        private const val RIGHT_VIEW_ID = 2
+        private const val START_VIEW_ID = 1
+        private const val END_VIEW_ID = 2
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentViewTest.java
index 682ff1f..81b8e98 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentViewTest.java
@@ -32,6 +32,7 @@
 import androidx.test.runner.AndroidJUnit4;
 
 import com.android.internal.R;
+import com.android.internal.widget.NotificationActionListLayout;
 import com.android.internal.widget.NotificationExpandButton;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.media.dialog.MediaOutputDialogFactory;
@@ -142,4 +143,60 @@
         verify(mockExpandedEB, times(1)).requestAccessibilityFocus();
         verify(mockHeadsUpEB, times(0)).requestAccessibilityFocus();
     }
+
+    @Test
+    @UiThreadTest
+    public void testRemoteInputVisibleSetsActionsUnimportantHideDescendantsForAccessibility() {
+        View mockContracted = mock(NotificationHeaderView.class);
+
+        View mockExpandedActions = mock(NotificationActionListLayout.class);
+        View mockExpanded = mock(NotificationHeaderView.class);
+        when(mockExpanded.findViewById(com.android.internal.R.id.actions)).thenReturn(
+                mockExpandedActions);
+
+        View mockHeadsUpActions = mock(NotificationActionListLayout.class);
+        View mockHeadsUp = mock(NotificationHeaderView.class);
+        when(mockHeadsUp.findViewById(com.android.internal.R.id.actions)).thenReturn(
+                mockHeadsUpActions);
+
+        mView.setContractedChild(mockContracted);
+        mView.setExpandedChild(mockExpanded);
+        mView.setHeadsUpChild(mockHeadsUp);
+
+        mView.setRemoteInputVisible(true);
+
+        verify(mockContracted, times(0)).findViewById(0);
+        verify(mockExpandedActions, times(1)).setImportantForAccessibility(
+                View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS);
+        verify(mockHeadsUpActions, times(1)).setImportantForAccessibility(
+                View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS);
+    }
+
+    @Test
+    @UiThreadTest
+    public void testRemoteInputInvisibleSetsActionsAutoImportantForAccessibility() {
+        View mockContracted = mock(NotificationHeaderView.class);
+
+        View mockExpandedActions = mock(NotificationActionListLayout.class);
+        View mockExpanded = mock(NotificationHeaderView.class);
+        when(mockExpanded.findViewById(com.android.internal.R.id.actions)).thenReturn(
+                mockExpandedActions);
+
+        View mockHeadsUpActions = mock(NotificationActionListLayout.class);
+        View mockHeadsUp = mock(NotificationHeaderView.class);
+        when(mockHeadsUp.findViewById(com.android.internal.R.id.actions)).thenReturn(
+                mockHeadsUpActions);
+
+        mView.setContractedChild(mockContracted);
+        mView.setExpandedChild(mockExpanded);
+        mView.setHeadsUpChild(mockHeadsUp);
+
+        mView.setRemoteInputVisible(false);
+
+        verify(mockContracted, times(0)).findViewById(0);
+        verify(mockExpandedActions, times(1)).setImportantForAccessibility(
+                View.IMPORTANT_FOR_ACCESSIBILITY_AUTO);
+        verify(mockHeadsUpActions, times(1)).setImportantForAccessibility(
+                View.IMPORTANT_FOR_ACCESSIBILITY_AUTO);
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java
index 8be9eb5..1c9b0be 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java
@@ -45,6 +45,7 @@
 import com.android.systemui.classifier.FalsingCollectorFake;
 import com.android.systemui.classifier.FalsingManagerFake;
 import com.android.systemui.dump.DumpManager;
+import com.android.systemui.flags.FeatureFlags;
 import com.android.systemui.media.KeyguardMediaController;
 import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin;
 import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin.OnMenuEventListener;
@@ -127,6 +128,7 @@
     @Mock private NotificationStackScrollLogger mLogger;
     @Mock private NotificationStackSizeCalculator mNotificationStackSizeCalculator;
     @Mock private ShadeTransitionController mShadeTransitionController;
+    @Mock private FeatureFlags mFeatureFlags;
 
     @Captor
     private ArgumentCaptor<StatusBarStateController.StateListener> mStateListenerArgumentCaptor;
@@ -174,7 +176,8 @@
                 mJankMonitor,
                 mStackLogger,
                 mLogger,
-                mNotificationStackSizeCalculator
+                mNotificationStackSizeCalculator,
+                mFeatureFlags
         );
 
         when(mNotificationStackScrollLayout.isAttachedToWindow()).thenReturn(true);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelperTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelperTest.java
index 1305d79..4ea1c71 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelperTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelperTest.java
@@ -18,10 +18,13 @@
 import static junit.framework.Assert.assertTrue;
 
 import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyFloat;
 import static org.mockito.Mockito.doAnswer;
 import static org.mockito.Mockito.doNothing;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
@@ -51,10 +54,15 @@
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
 import org.mockito.junit.MockitoJUnit;
 import org.mockito.junit.MockitoRule;
 import org.mockito.stubbing.Answer;
 
+import java.util.Arrays;
+import java.util.List;
+import java.util.stream.Collectors;
+
 /**
  * Tests for {@link NotificationSwipeHelper}.
  */
@@ -74,7 +82,11 @@
     private Runnable mFalsingCheck;
     private FeatureFlags mFeatureFlags;
 
-    @Rule public MockitoRule mockito = MockitoJUnit.rule();
+    private static final int FAKE_ROW_WIDTH = 20;
+    private static final int FAKE_ROW_HEIGHT = 20;
+
+    @Rule
+    public MockitoRule mockito = MockitoJUnit.rule();
 
     @Before
     public void setUp() throws Exception {
@@ -444,8 +456,8 @@
         doReturn(5f).when(mEvent).getRawX();
         doReturn(10f).when(mEvent).getRawY();
 
-        doReturn(20).when(mView).getWidth();
-        doReturn(20).when(mView).getHeight();
+        doReturn(FAKE_ROW_WIDTH).when(mView).getWidth();
+        doReturn(FAKE_ROW_HEIGHT).when(mView).getHeight();
 
         Answer answer = (Answer) invocation -> {
             int[] arr = invocation.getArgument(0);
@@ -472,8 +484,8 @@
         doReturn(5f).when(mEvent).getRawX();
         doReturn(10f).when(mEvent).getRawY();
 
-        doReturn(20).when(mNotificationRow).getWidth();
-        doReturn(20).when(mNotificationRow).getActualHeight();
+        doReturn(FAKE_ROW_WIDTH).when(mNotificationRow).getWidth();
+        doReturn(FAKE_ROW_HEIGHT).when(mNotificationRow).getActualHeight();
 
         Answer answer = (Answer) invocation -> {
             int[] arr = invocation.getArgument(0);
@@ -491,4 +503,56 @@
         assertFalse("Touch is not within the view",
                 mSwipeHelper.isTouchInView(mEvent, mNotificationRow));
     }
+
+    @Test
+    public void testContentAlphaRemainsUnchangedWhenNotificationIsNotDismissible() {
+        doReturn(FAKE_ROW_WIDTH).when(mNotificationRow).getMeasuredWidth();
+
+        mSwipeHelper.onTranslationUpdate(mNotificationRow, 12, false);
+
+        verify(mNotificationRow, never()).setContentAlpha(anyFloat());
+    }
+
+    @Test
+    public void testContentAlphaRemainsUnchangedWhenFeatureFlagIsDisabled() {
+
+        // Returning true prevents alpha fade. In an unmocked scenario the callback is instantiated
+        // within NotificationStackScrollLayoutController and returns the inverted value of the
+        // feature flag
+        doReturn(true).when(mCallback).updateSwipeProgress(any(), anyBoolean(), anyFloat());
+        doReturn(FAKE_ROW_WIDTH).when(mNotificationRow).getMeasuredWidth();
+
+        mSwipeHelper.onTranslationUpdate(mNotificationRow, 12, true);
+
+        verify(mNotificationRow, never()).setContentAlpha(anyFloat());
+    }
+
+    @Test
+    public void testContentAlphaFadeAnimationSpecs() {
+        // The alpha fade should be linear from 1f to 0f as translation progresses from 0 to 60% of
+        // view-width, and 0f at translations higher than that.
+        doReturn(FAKE_ROW_WIDTH).when(mNotificationRow).getMeasuredWidth();
+
+        List<Integer> translations = Arrays.asList(
+                -FAKE_ROW_WIDTH * 2,
+                -FAKE_ROW_WIDTH,
+                (int) (-FAKE_ROW_WIDTH * 0.3),
+                0,
+                (int) (FAKE_ROW_WIDTH * 0.3),
+                (int) (FAKE_ROW_WIDTH * 0.6),
+                FAKE_ROW_WIDTH,
+                FAKE_ROW_WIDTH * 2);
+        List<Float> expectedAlphas = translations.stream().map(translation ->
+                        mSwipeHelper.getSwipeAlpha(Math.abs((float) translation / FAKE_ROW_WIDTH)))
+                .collect(Collectors.toList());
+
+        for (Integer translation : translations) {
+            mSwipeHelper.onTranslationUpdate(mNotificationRow, translation, true);
+        }
+
+        ArgumentCaptor<Float> capturedValues = ArgumentCaptor.forClass(Float.class);
+        verify(mNotificationRow, times(translations.size())).setContentAlpha(
+                capturedValues.capture());
+        assertEquals(expectedAlphas, capturedValues.getAllValues());
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
index f52bfca..5a12fcb1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
@@ -16,6 +16,8 @@
 
 package com.android.systemui.statusbar.phone;
 
+import static com.android.systemui.flags.Flags.MODERN_BOUNCER;
+
 import static org.junit.Assert.assertTrue;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyBoolean;
@@ -23,11 +25,9 @@
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.clearInvocations;
-import static org.mockito.Mockito.doAnswer;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.reset;
-import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
@@ -528,25 +528,10 @@
         verify(mCentralSurfaces).setBouncerShowingOverDream(false);
     }
 
-
     @Test
-    public void testSetDozing_bouncerShowing_Dozing() {
-        clearInvocations(mBouncer);
-        when(mBouncer.isShowing()).thenReturn(true);
-        doAnswer(invocation -> {
-            when(mBouncer.isShowing()).thenReturn(false);
-            return null;
-        }).when(mBouncer).hide(false);
-        mStatusBarKeyguardViewManager.onDozingChanged(true);
-        verify(mBouncer, times(1)).hide(false);
-    }
-
-    @Test
-    public void testSetDozing_bouncerShowing_notDozing() {
-        mStatusBarKeyguardViewManager.onDozingChanged(true);
-        when(mBouncer.isShowing()).thenReturn(true);
-        clearInvocations(mBouncer);
-        mStatusBarKeyguardViewManager.onDozingChanged(false);
-        verify(mBouncer, times(1)).hide(false);
+    public void flag_off_DoesNotCallBouncerInteractor() {
+        when(mFeatureFlags.isEnabled(MODERN_BOUNCER)).thenReturn(false);
+        mStatusBarKeyguardViewManager.hideBouncer(false);
+        verify(mBouncerInteractor, never()).hide();
     }
 }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestCase.java b/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestCase.java
index c83189d..fa3cc99 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestCase.java
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestCase.java
@@ -84,9 +84,14 @@
         initializer.init(true);
         mDependency = new TestableDependency(initializer.getSysUIComponent().createDependency());
         Dependency.setInstance(mDependency);
-        mFakeBroadcastDispatcher = new FakeBroadcastDispatcher(mContext, mock(Looper.class),
-                mock(Executor.class), mock(DumpManager.class),
-                mock(BroadcastDispatcherLogger.class), mock(UserTracker.class));
+        mFakeBroadcastDispatcher = new FakeBroadcastDispatcher(
+                mContext,
+                mContext.getMainExecutor(),
+                mock(Looper.class),
+                mock(Executor.class),
+                mock(DumpManager.class),
+                mock(BroadcastDispatcherLogger.class),
+                mock(UserTracker.class));
 
         mRealInstrumentation = InstrumentationRegistry.getInstrumentation();
         Instrumentation inst = spy(mRealInstrumentation);
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/broadcast/FakeBroadcastDispatcher.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/broadcast/FakeBroadcastDispatcher.kt
index bb646f0..52e0c982 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/broadcast/FakeBroadcastDispatcher.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/broadcast/FakeBroadcastDispatcher.kt
@@ -32,16 +32,18 @@
 
 class FakeBroadcastDispatcher(
     context: SysuiTestableContext,
-    looper: Looper,
-    executor: Executor,
+    mainExecutor: Executor,
+    broadcastRunningLooper: Looper,
+    broadcastRunningExecutor: Executor,
     dumpManager: DumpManager,
     logger: BroadcastDispatcherLogger,
     userTracker: UserTracker
 ) :
     BroadcastDispatcher(
         context,
-        looper,
-        executor,
+        mainExecutor,
+        broadcastRunningLooper,
+        broadcastRunningExecutor,
         dumpManager,
         logger,
         userTracker,
diff --git a/packages/SystemUI/unfold/src/com/android/systemui/unfold/progress/PhysicsBasedUnfoldTransitionProgressProvider.kt b/packages/SystemUI/unfold/src/com/android/systemui/unfold/progress/PhysicsBasedUnfoldTransitionProgressProvider.kt
index 2ab28c6..043aff6 100644
--- a/packages/SystemUI/unfold/src/com/android/systemui/unfold/progress/PhysicsBasedUnfoldTransitionProgressProvider.kt
+++ b/packages/SystemUI/unfold/src/com/android/systemui/unfold/progress/PhysicsBasedUnfoldTransitionProgressProvider.kt
@@ -203,6 +203,6 @@
 private const val TAG = "PhysicsBasedUnfoldTransitionProgressProvider"
 private const val DEBUG = true
 
-private const val SPRING_STIFFNESS = 200.0f
+private const val SPRING_STIFFNESS = 600.0f
 private const val MINIMAL_VISIBLE_CHANGE = 0.001f
 private const val FINAL_HINGE_ANGLE_POSITION = 165f
diff --git a/services/core/java/com/android/server/power/Notifier.java b/services/core/java/com/android/server/power/Notifier.java
index 5a2fb18..dad9584 100644
--- a/services/core/java/com/android/server/power/Notifier.java
+++ b/services/core/java/com/android/server/power/Notifier.java
@@ -571,7 +571,8 @@
     /**
      * Called when there has been user activity.
      */
-    public void onUserActivity(int displayGroupId, int event, int uid) {
+    public void onUserActivity(int displayGroupId, @PowerManager.UserActivityEvent int event,
+            int uid) {
         if (DEBUG) {
             Slog.d(TAG, "onUserActivity: event=" + event + ", uid=" + uid);
         }
diff --git a/services/core/java/com/android/server/power/PowerGroup.java b/services/core/java/com/android/server/power/PowerGroup.java
index fec61ac..9fe53fb 100644
--- a/services/core/java/com/android/server/power/PowerGroup.java
+++ b/services/core/java/com/android/server/power/PowerGroup.java
@@ -74,6 +74,8 @@
     private long mLastPowerOnTime;
     private long mLastUserActivityTime;
     private long mLastUserActivityTimeNoChangeLights;
+    @PowerManager.UserActivityEvent
+    private int mLastUserActivityEvent;
     /** Timestamp (milliseconds since boot) of the last time the power group was awoken.*/
     private long mLastWakeTime;
     /** Timestamp (milliseconds since boot) of the last time the power group was put to sleep. */
@@ -244,7 +246,7 @@
         return true;
     }
 
-    boolean dozeLocked(long eventTime, int uid, int reason) {
+    boolean dozeLocked(long eventTime, int uid, @PowerManager.GoToSleepReason int reason) {
         if (eventTime < getLastWakeTimeLocked() || !isInteractive(mWakefulness)) {
             return false;
         }
@@ -253,9 +255,14 @@
         try {
             reason = Math.min(PowerManager.GO_TO_SLEEP_REASON_MAX,
                     Math.max(reason, PowerManager.GO_TO_SLEEP_REASON_MIN));
+            long millisSinceLastUserActivity = eventTime - Math.max(
+                    mLastUserActivityTimeNoChangeLights, mLastUserActivityTime);
             Slog.i(TAG, "Powering off display group due to "
-                    + PowerManager.sleepReasonToString(reason)  + " (groupId= " + getGroupId()
-                    + ", uid= " + uid + ")...");
+                    + PowerManager.sleepReasonToString(reason)
+                    + " (groupId= " + getGroupId() + ", uid= " + uid
+                    + ", millisSinceLastUserActivity=" + millisSinceLastUserActivity
+                    + ", lastUserActivityEvent=" + PowerManager.userActivityEventToString(
+                    mLastUserActivityEvent) + ")...");
 
             setSandmanSummonedLocked(/* isSandmanSummoned= */ true);
             setWakefulnessLocked(WAKEFULNESS_DOZING, eventTime, uid, reason, /* opUid= */ 0,
@@ -266,14 +273,16 @@
         return true;
     }
 
-    boolean sleepLocked(long eventTime, int uid, int reason) {
+    boolean sleepLocked(long eventTime, int uid, @PowerManager.GoToSleepReason int reason) {
         if (eventTime < mLastWakeTime || getWakefulnessLocked() == WAKEFULNESS_ASLEEP) {
             return false;
         }
 
         Trace.traceBegin(Trace.TRACE_TAG_POWER, "sleepPowerGroup");
         try {
-            Slog.i(TAG, "Sleeping power group (groupId=" + getGroupId() + ", uid=" + uid + ")...");
+            Slog.i(TAG,
+                    "Sleeping power group (groupId=" + getGroupId() + ", uid=" + uid + ", reason="
+                            + PowerManager.sleepReasonToString(reason) + ")...");
             setSandmanSummonedLocked(/* isSandmanSummoned= */ true);
             setWakefulnessLocked(WAKEFULNESS_ASLEEP, eventTime, uid, reason, /* opUid= */0,
                     /* opPackageName= */ null, /* details= */ null);
@@ -287,16 +296,20 @@
         return mLastUserActivityTime;
     }
 
-    void setLastUserActivityTimeLocked(long lastUserActivityTime) {
+    void setLastUserActivityTimeLocked(long lastUserActivityTime,
+            @PowerManager.UserActivityEvent int event) {
         mLastUserActivityTime = lastUserActivityTime;
+        mLastUserActivityEvent = event;
     }
 
     public long getLastUserActivityTimeNoChangeLightsLocked() {
         return mLastUserActivityTimeNoChangeLights;
     }
 
-    public void setLastUserActivityTimeNoChangeLightsLocked(long time) {
+    public void setLastUserActivityTimeNoChangeLightsLocked(long time,
+            @PowerManager.UserActivityEvent int event) {
         mLastUserActivityTimeNoChangeLights = time;
+        mLastUserActivityEvent = event;
     }
 
     public int getUserActivitySummaryLocked() {
diff --git a/services/core/java/com/android/server/power/PowerManagerService.java b/services/core/java/com/android/server/power/PowerManagerService.java
index dbf05f1..3a05fc3 100644
--- a/services/core/java/com/android/server/power/PowerManagerService.java
+++ b/services/core/java/com/android/server/power/PowerManagerService.java
@@ -1169,6 +1169,7 @@
                 return;
             }
 
+            Slog.i(TAG, "onFlip(): Face " + (isFaceDown ? "down." : "up."));
             mIsFaceDown = isFaceDown;
             if (isFaceDown) {
                 final long currentTime = mClock.uptimeMillis();
@@ -1888,12 +1889,13 @@
 
     // Called from native code.
     @SuppressWarnings("unused")
-    private void userActivityFromNative(long eventTime, int event, int displayId, int flags) {
+    private void userActivityFromNative(long eventTime, @PowerManager.UserActivityEvent int event,
+            int displayId, int flags) {
         userActivityInternal(displayId, eventTime, event, flags, Process.SYSTEM_UID);
     }
 
-    private void userActivityInternal(int displayId, long eventTime, int event, int flags,
-            int uid) {
+    private void userActivityInternal(int displayId, long eventTime,
+            @PowerManager.UserActivityEvent int event, int flags, int uid) {
         synchronized (mLock) {
             if (displayId == Display.INVALID_DISPLAY) {
                 if (userActivityNoUpdateLocked(eventTime, event, flags, uid)) {
@@ -1944,11 +1946,12 @@
 
     @GuardedBy("mLock")
     private boolean userActivityNoUpdateLocked(final PowerGroup powerGroup, long eventTime,
-            int event, int flags, int uid) {
+            @PowerManager.UserActivityEvent int event, int flags, int uid) {
         final int groupId = powerGroup.getGroupId();
         if (DEBUG_SPEW) {
             Slog.d(TAG, "userActivityNoUpdateLocked: groupId=" + groupId
-                    + ", eventTime=" + eventTime + ", event=" + event
+                    + ", eventTime=" + eventTime
+                    + ", event=" + PowerManager.userActivityEventToString(event)
                     + ", flags=0x" + Integer.toHexString(flags) + ", uid=" + uid);
         }
 
@@ -1983,7 +1986,7 @@
             if ((flags & PowerManager.USER_ACTIVITY_FLAG_NO_CHANGE_LIGHTS) != 0) {
                 if (eventTime > powerGroup.getLastUserActivityTimeNoChangeLightsLocked()
                         && eventTime > powerGroup.getLastUserActivityTimeLocked()) {
-                    powerGroup.setLastUserActivityTimeNoChangeLightsLocked(eventTime);
+                    powerGroup.setLastUserActivityTimeNoChangeLightsLocked(eventTime, event);
                     mDirty |= DIRTY_USER_ACTIVITY;
                     if (event == PowerManager.USER_ACTIVITY_EVENT_BUTTON) {
                         mDirty |= DIRTY_QUIESCENT;
@@ -1993,7 +1996,7 @@
                 }
             } else {
                 if (eventTime > powerGroup.getLastUserActivityTimeLocked()) {
-                    powerGroup.setLastUserActivityTimeLocked(eventTime);
+                    powerGroup.setLastUserActivityTimeLocked(eventTime, event);
                     mDirty |= DIRTY_USER_ACTIVITY;
                     if (event == PowerManager.USER_ACTIVITY_EVENT_BUTTON) {
                         mDirty |= DIRTY_QUIESCENT;
@@ -2020,7 +2023,8 @@
             @WakeReason int reason, String details, int uid, String opPackageName, int opUid) {
         if (DEBUG_SPEW) {
             Slog.d(TAG, "wakePowerGroupLocked: eventTime=" + eventTime
-                    + ", groupId=" + powerGroup.getGroupId() + ", uid=" + uid);
+                    + ", groupId=" + powerGroup.getGroupId()
+                    + ", reason=" + PowerManager.wakeReasonToString(reason) + ", uid=" + uid);
         }
         if (mForceSuspendActive || !mSystemReady) {
             return;
@@ -2043,11 +2047,11 @@
 
     @GuardedBy("mLock")
     private boolean dozePowerGroupLocked(final PowerGroup powerGroup, long eventTime,
-            int reason, int uid) {
+            @GoToSleepReason int reason, int uid) {
         if (DEBUG_SPEW) {
             Slog.d(TAG, "dozePowerGroup: eventTime=" + eventTime
-                    + ", groupId=" + powerGroup.getGroupId() + ", reason=" + reason
-                    + ", uid=" + uid);
+                    + ", groupId=" + powerGroup.getGroupId()
+                    + ", reason=" + PowerManager.sleepReasonToString(reason) + ", uid=" + uid);
         }
 
         if (!mSystemReady || !mBootCompleted) {
@@ -2058,10 +2062,12 @@
     }
 
     @GuardedBy("mLock")
-    private boolean sleepPowerGroupLocked(final PowerGroup powerGroup, long eventTime, int reason,
-            int uid) {
+    private boolean sleepPowerGroupLocked(final PowerGroup powerGroup, long eventTime,
+            @GoToSleepReason int reason, int uid) {
         if (DEBUG_SPEW) {
-            Slog.d(TAG, "sleepPowerGroup: eventTime=" + eventTime + ", uid=" + uid);
+            Slog.d(TAG, "sleepPowerGroup: eventTime=" + eventTime
+                    + ", groupId=" + powerGroup.getGroupId()
+                    + ", reason=" + PowerManager.sleepReasonToString(reason) + ", uid=" + uid);
         }
         if (!mBootCompleted || !mSystemReady) {
             return false;
@@ -2122,8 +2128,10 @@
             case WAKEFULNESS_DOZING:
                 traceMethodName = "goToSleep";
                 Slog.i(TAG, "Going to sleep due to " + PowerManager.sleepReasonToString(reason)
-                        + " (uid " + uid + ")...");
-
+                        + " (uid " + uid + ", screenOffTimeout=" + mScreenOffTimeoutSetting
+                        + ", activityTimeoutWM=" + mUserActivityTimeoutOverrideFromWindowManager
+                        + ", maxDimRatio=" + mMaximumScreenDimRatioConfig
+                        + ", maxDimDur=" + mMaximumScreenDimDurationConfig + ")...");
                 mLastGlobalSleepTime = eventTime;
                 mLastGlobalSleepReason = reason;
                 mDozeStartInProgress = true;
@@ -4207,7 +4215,7 @@
     void onUserActivity() {
         synchronized (mLock) {
             mPowerGroups.get(Display.DEFAULT_DISPLAY_GROUP).setLastUserActivityTimeLocked(
-                    mClock.uptimeMillis());
+                    mClock.uptimeMillis(), PowerManager.USER_ACTIVITY_EVENT_OTHER);
         }
     }
 
@@ -5590,7 +5598,8 @@
         }
 
         @Override // Binder call
-        public void userActivity(int displayId, long eventTime, int event, int flags) {
+        public void userActivity(int displayId, long eventTime,
+                @PowerManager.UserActivityEvent int event, int flags) {
             final long now = mClock.uptimeMillis();
             if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.DEVICE_POWER)
                     != PackageManager.PERMISSION_GRANTED
diff --git a/services/core/java/com/android/server/trust/TrustManagerService.java b/services/core/java/com/android/server/trust/TrustManagerService.java
index 7170773..2888b9a 100644
--- a/services/core/java/com/android/server/trust/TrustManagerService.java
+++ b/services/core/java/com/android/server/trust/TrustManagerService.java
@@ -602,9 +602,12 @@
         synchronized (mUserTrustState) {
             wasTrusted = (mUserTrustState.get(userId) == TrustState.TRUSTED);
             wasTrustable = (mUserTrustState.get(userId) == TrustState.TRUSTABLE);
+            boolean isAutomotive = getContext().getPackageManager().hasSystemFeature(
+                    PackageManager.FEATURE_AUTOMOTIVE);
             boolean renewingTrust = wasTrustable && (
                     (flags & TrustAgentService.FLAG_GRANT_TRUST_TEMPORARY_AND_RENEWABLE) != 0);
-            boolean canMoveToTrusted = alreadyUnlocked || isFromUnlock || renewingTrust;
+            boolean canMoveToTrusted =
+                    alreadyUnlocked || isFromUnlock || renewingTrust || isAutomotive;
             boolean upgradingTrustForCurrentUser = (userId == mCurrentUser);
 
             if (trustedByAtLeastOneAgent && wasTrusted) {
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index fab1683..9c080e8 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -5818,8 +5818,8 @@
         // in untrusted mode. Traverse bottom to top with boundary so that it will only check
         // activities above this activity.
         final ActivityRecord differentUidOverlayActivity = getTask().getActivity(
-                a -> a.getUid() != getUid(), this /* boundary */, false /* includeBoundary */,
-                false /* traverseTopToBottom */);
+                a -> !a.finishing && a.getUid() != getUid(), this /* boundary */,
+                false /* includeBoundary */, false /* traverseTopToBottom */);
         return differentUidOverlayActivity != null;
     }
 
diff --git a/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
index e976fd4..7342c49 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
@@ -19,7 +19,6 @@
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
 import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
 import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
-import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_NOSENSOR;
 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSET;
 import static android.view.WindowManager.LayoutParams.ROTATION_ANIMATION_SEAMLESS;
@@ -1229,10 +1228,8 @@
         final ArraySet<WindowContainer> participants = transition.mParticipants;
 
         final Task task = createTask(mDisplayContent);
-        // Set to multi-windowing mode in order to set bounds.
-        task.setWindowingMode(WINDOWING_MODE_MULTI_WINDOW);
         final Rect taskBounds = new Rect(0, 0, 500, 1000);
-        task.setBounds(taskBounds);
+        task.getConfiguration().windowConfiguration.setBounds(taskBounds);
         final ActivityRecord nonEmbeddedActivity = createActivityRecord(task);
         final TaskFragmentOrganizer organizer = new TaskFragmentOrganizer(Runnable::run);
         mAtm.mTaskFragmentOrganizerController.registerOrganizer(
@@ -1274,10 +1271,8 @@
         final ArraySet<WindowContainer> participants = transition.mParticipants;
 
         final Task task = createTask(mDisplayContent);
-        // Set to multi-windowing mode in order to set bounds.
-        task.setWindowingMode(WINDOWING_MODE_MULTI_WINDOW);
         final Rect taskBounds = new Rect(0, 0, 500, 1000);
-        task.setBounds(taskBounds);
+        task.getConfiguration().windowConfiguration.setBounds(taskBounds);
         final ActivityRecord activity = createActivityRecord(task);
         // Start states: set bounds to make sure the start bounds is ignored if it is not visible.
         activity.getConfiguration().windowConfiguration.setBounds(new Rect(0, 0, 250, 500));
@@ -1305,10 +1300,8 @@
         final ArraySet<WindowContainer> participants = transition.mParticipants;
 
         final Task task = createTask(mDisplayContent);
-        // Set to multi-windowing mode in order to set bounds.
-        task.setWindowingMode(WINDOWING_MODE_MULTI_WINDOW);
         final Rect taskBounds = new Rect(0, 0, 500, 1000);
-        task.setBounds(taskBounds);
+        task.getConfiguration().windowConfiguration.setBounds(taskBounds);
         final ActivityRecord activity = createActivityRecord(task);
         // Start states: fills Task without override.
         activity.mVisibleRequested = true;
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/AssistantAppHelper.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/AssistantAppHelper.kt
new file mode 100644
index 0000000..efb92f2
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/AssistantAppHelper.kt
@@ -0,0 +1,80 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm.flicker.helpers
+
+import android.app.Instrumentation
+import android.content.ComponentName
+import android.provider.Settings
+import androidx.test.uiautomator.By
+import androidx.test.uiautomator.UiDevice
+import androidx.test.uiautomator.Until
+import com.android.server.wm.flicker.testapp.ActivityOptions
+import org.junit.Assert.assertNotNull
+
+class AssistantAppHelper @JvmOverloads constructor(
+    val instr: Instrumentation,
+    val component: ComponentName = ActivityOptions.ASSISTANT_SERVICE_COMPONENT_NAME,
+) {
+    protected val uiDevice: UiDevice = UiDevice.getInstance(instr)
+    protected val defaultAssistant: String? = Settings.Secure.getString(
+        instr.targetContext.contentResolver,
+        Settings.Secure.ASSISTANT)
+    protected val defaultVoiceInteractionService: String? = Settings.Secure.getString(
+        instr.targetContext.contentResolver,
+        Settings.Secure.VOICE_INTERACTION_SERVICE)
+
+    fun setDefaultAssistant() {
+        Settings.Secure.putString(
+            instr.targetContext.contentResolver,
+            Settings.Secure.VOICE_INTERACTION_SERVICE,
+            component.flattenToString())
+        Settings.Secure.putString(
+            instr.targetContext.contentResolver,
+            Settings.Secure.ASSISTANT,
+            component.flattenToString())
+    }
+
+    fun resetDefaultAssistant() {
+        Settings.Secure.putString(
+            instr.targetContext.contentResolver,
+            Settings.Secure.VOICE_INTERACTION_SERVICE,
+            defaultVoiceInteractionService)
+        Settings.Secure.putString(
+            instr.targetContext.contentResolver,
+            Settings.Secure.ASSISTANT,
+            defaultAssistant)
+    }
+
+    /**
+     * Open Assistance UI.
+     *
+     * @param longpress open the UI by long pressing power button.
+     *  Otherwise open the UI through vioceinteraction shell command directly.
+     */
+    @JvmOverloads
+    fun openUI(longpress: Boolean = false) {
+        if (longpress) {
+            uiDevice.executeShellCommand("input keyevent --longpress KEYCODE_POWER")
+        } else {
+            uiDevice.executeShellCommand("cmd voiceinteraction show")
+        }
+        val ui = uiDevice.wait(
+            Until.findObject(By.res(ActivityOptions.FLICKER_APP_PACKAGE, "vis_frame")),
+            FIND_TIMEOUT)
+        assertNotNull("Can't find Assistant UI after long pressing power button.", ui)
+    }
+}
diff --git a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityOptions.java b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityOptions.java
index 45a4730..e90eed1 100644
--- a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityOptions.java
+++ b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityOptions.java
@@ -80,20 +80,21 @@
 
     public static final String SHOW_WHEN_LOCKED_ACTIVITY_LAUNCHER_NAME = "ShowWhenLockedApp";
     public static final ComponentName SHOW_WHEN_LOCKED_ACTIVITY_COMPONENT_NAME =
-            new ComponentName(FLICKER_APP_PACKAGE,
-                    FLICKER_APP_PACKAGE + ".ShowWhenLockedActivity");
+            new ComponentName(FLICKER_APP_PACKAGE, FLICKER_APP_PACKAGE + ".ShowWhenLockedActivity");
 
     public static final String NOTIFICATION_ACTIVITY_LAUNCHER_NAME = "NotificationApp";
     public static final ComponentName NOTIFICATION_ACTIVITY_COMPONENT_NAME =
-            new ComponentName(FLICKER_APP_PACKAGE,
-                    FLICKER_APP_PACKAGE + ".NotificationActivity");
+            new ComponentName(FLICKER_APP_PACKAGE, FLICKER_APP_PACKAGE + ".NotificationActivity");
 
     public static final String MAIL_ACTIVITY_LAUNCHER_NAME = "MailActivity";
-    public static final ComponentName MAIL_ACTIVITY_COMPONENT_NAME = new ComponentName(
-            FLICKER_APP_PACKAGE, FLICKER_APP_PACKAGE + ".MailActivity");
+    public static final ComponentName MAIL_ACTIVITY_COMPONENT_NAME =
+            new ComponentName(FLICKER_APP_PACKAGE, FLICKER_APP_PACKAGE + ".MailActivity");
 
     public static final String GAME_ACTIVITY_LAUNCHER_NAME = "GameApp";
     public static final ComponentName GAME_ACTIVITY_COMPONENT_NAME =
-            new ComponentName(FLICKER_APP_PACKAGE,
-                   FLICKER_APP_PACKAGE + ".GameActivity");
+            new ComponentName(FLICKER_APP_PACKAGE, FLICKER_APP_PACKAGE + ".GameActivity");
+
+    public static final ComponentName ASSISTANT_SERVICE_COMPONENT_NAME =
+            new ComponentName(
+                    FLICKER_APP_PACKAGE, FLICKER_APP_PACKAGE + ".AssistantInteractionService");
 }