Add interactive view above status bar.

Adds an invisible view layered above status bar to forward input through
the existing handleCaptionThroughStatusBar logic. This reduces our
reliance on a global input monitor and also fixes an issue where desktop
mode could be invoked by dragging down in handle coordinates even while
notification shade is active.

Bug: 349135068
Bug: 341997116
Test: manual
Flag: com.android.window.flags.enable_additional_windows_above_status_bar
Change-Id: I94120c64c7f359661fca632df0251989cd93fa07
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
index a91edaa..de901b5 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
@@ -69,6 +69,7 @@
 import com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT
 import com.android.wm.shell.compatui.isTopActivityExemptFromDesktopWindowing
 import com.android.wm.shell.desktopmode.DesktopModeTaskRepository.VisibleTasksListener
+import com.android.wm.shell.desktopmode.DesktopModeVisualIndicator.IndicatorType
 import com.android.wm.shell.desktopmode.DragToDesktopTransitionHandler.DragToDesktopStateListener
 import com.android.wm.shell.draganddrop.DragAndDropController
 import com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE
@@ -1334,33 +1335,36 @@
      *
      * @param taskInfo the task being dragged.
      * @param y height of drag, to be checked against status bar height.
+     * @return the [IndicatorType] used for the resulting transition
      */
     fun onDragPositioningEndThroughStatusBar(
         inputCoordinates: PointF,
         taskInfo: RunningTaskInfo,
-    ) {
-        val indicator = getVisualIndicator() ?: return
+    ): IndicatorType {
+        val indicator = getVisualIndicator() ?: return IndicatorType.NO_INDICATOR
         val indicatorType = indicator.updateIndicatorType(inputCoordinates, taskInfo.windowingMode)
         when (indicatorType) {
-            DesktopModeVisualIndicator.IndicatorType.TO_DESKTOP_INDICATOR -> {
-                val displayLayout = displayController.getDisplayLayout(taskInfo.displayId) ?: return
+            IndicatorType.TO_DESKTOP_INDICATOR -> {
+                val displayLayout = displayController.getDisplayLayout(taskInfo.displayId)
+                    ?: return IndicatorType.NO_INDICATOR
                 if (Flags.enableWindowingDynamicInitialBounds()) {
                     finalizeDragToDesktop(taskInfo, calculateInitialBounds(displayLayout, taskInfo))
                 } else {
                     finalizeDragToDesktop(taskInfo, getDefaultDesktopTaskBounds(displayLayout))
                 }
             }
-            DesktopModeVisualIndicator.IndicatorType.NO_INDICATOR,
-            DesktopModeVisualIndicator.IndicatorType.TO_FULLSCREEN_INDICATOR -> {
+            IndicatorType.NO_INDICATOR,
+            IndicatorType.TO_FULLSCREEN_INDICATOR -> {
                 cancelDragToDesktop(taskInfo)
             }
-            DesktopModeVisualIndicator.IndicatorType.TO_SPLIT_LEFT_INDICATOR -> {
+            IndicatorType.TO_SPLIT_LEFT_INDICATOR -> {
                 requestSplit(taskInfo, leftOrTop = true)
             }
-            DesktopModeVisualIndicator.IndicatorType.TO_SPLIT_RIGHT_INDICATOR -> {
+            IndicatorType.TO_SPLIT_RIGHT_INDICATOR -> {
                 requestSplit(taskInfo, leftOrTop = false)
             }
         }
+        return indicatorType
     }
 
     /** Update the exclusion region for a specified task */
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
index 3f0e875..2570372 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
@@ -36,6 +36,8 @@
 import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT;
 import static com.android.wm.shell.compatui.AppCompatUtils.isTopActivityExemptFromDesktopWindowing;
 import static com.android.wm.shell.desktopmode.DesktopModeVisualIndicator.IndicatorType.TO_FULLSCREEN_INDICATOR;
+import static com.android.wm.shell.desktopmode.DesktopModeVisualIndicator.IndicatorType.TO_SPLIT_LEFT_INDICATOR;
+import static com.android.wm.shell.desktopmode.DesktopModeVisualIndicator.IndicatorType.TO_SPLIT_RIGHT_INDICATOR;
 import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE;
 import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_TYPE_UNDEFINED;
 
@@ -327,7 +329,8 @@
         if (decoration == null) return;
         final RunningTaskInfo oldTaskInfo = decoration.mTaskInfo;
 
-        if (taskInfo.displayId != oldTaskInfo.displayId) {
+        if (taskInfo.displayId != oldTaskInfo.displayId
+                && !Flags.enableAdditionalWindowsAboveStatusBar()) {
             removeTaskFromEventReceiver(oldTaskInfo.displayId);
             incrementEventReceiverTasks(taskInfo.displayId);
         }
@@ -391,7 +394,8 @@
 
         decoration.close();
         final int displayId = taskInfo.displayId;
-        if (mEventReceiversByDisplay.contains(displayId)) {
+        if (mEventReceiversByDisplay.contains(displayId)
+                && !Flags.enableAdditionalWindowsAboveStatusBar()) {
             removeTaskFromEventReceiver(displayId);
         }
         // Remove the decoration from the cache last because WindowDecoration#close could still
@@ -523,6 +527,9 @@
                 }
             } else if (id == R.id.split_screen_button) {
                 decoration.closeHandleMenu();
+                // When the app enters split-select, the handle will no longer be visible, meaning
+                // we shouldn't receive input for it any longer.
+                decoration.disposeStatusBarInputLayer();
                 mDesktopTasksController.requestSplit(decoration.mTaskInfo);
             } else if (id == R.id.open_in_browser_button) {
                 // TODO(b/346441962): let the decoration handle the click gesture and only call back
@@ -659,13 +666,38 @@
             final DesktopModeWindowDecoration decoration = mWindowDecorByTaskId.get(mTaskId);
             final RunningTaskInfo taskInfo = decoration.mTaskInfo;
             if (DesktopModeStatus.canEnterDesktopMode(mContext)
-                    && taskInfo.getWindowingMode() == WINDOWING_MODE_FULLSCREEN) {
-                return false;
+                    && !taskInfo.isFreeform()) {
+                return handleNonFreeformMotionEvent(decoration, v, e);
+            } else {
+                return handleFreeformMotionEvent(decoration, taskInfo, v, e);
             }
+        }
+
+        private boolean handleNonFreeformMotionEvent(DesktopModeWindowDecoration decoration,
+                View v, MotionEvent e) {
+            final int id = v.getId();
+            if (id == R.id.caption_handle) {
+                if (e.getActionMasked() == MotionEvent.ACTION_DOWN) {
+                    // Caption handle is located within the status bar region, meaning the
+                    // DisplayPolicy will attempt to transfer this input to status bar if it's
+                    // a swipe down. Pilfer here to keep the gesture in handle alone.
+                    mInputManager.pilferPointers(v.getViewRootImpl().getInputToken());
+                }
+                handleCaptionThroughStatusBar(e, decoration);
+                final boolean wasDragging = mIsDragging;
+                updateDragStatus(e.getActionMasked());
+                // Only prevent onClick from receiving this event if it's a drag.
+                return wasDragging;
+            }
+            return false;
+        }
+
+        private boolean handleFreeformMotionEvent(DesktopModeWindowDecoration decoration,
+                RunningTaskInfo taskInfo, View v, MotionEvent e) {
+            final int id = v.getId();
             if (mGestureDetector.onTouchEvent(e)) {
                 return true;
             }
-            final int id = v.getId();
             final boolean touchingButton = (id == R.id.close_window || id == R.id.maximize_window
                     || id == R.id.open_menu_button);
             switch (e.getActionMasked()) {
@@ -674,7 +706,7 @@
                     mDragPositioningCallback.onDragPositioningStart(
                             0 /* ctrlType */, e.getRawX(0),
                             e.getRawY(0));
-                    mIsDragging = false;
+                    updateDragStatus(e.getActionMasked());
                     mHasLongClicked = false;
                     // Do not consume input event if a button is touched, otherwise it would
                     // prevent the button's ripple effect from showing.
@@ -694,7 +726,7 @@
                             decoration.mTaskSurface,
                             e.getRawX(dragPointerIdx),
                             newTaskBounds);
-                    mIsDragging = true;
+                    updateDragStatus(e.getActionMasked());
                     return true;
                 }
                 case MotionEvent.ACTION_UP:
@@ -724,7 +756,7 @@
                         // onClick call that results.
                         return false;
                     } else {
-                        mIsDragging = false;
+                        updateDragStatus(e.getActionMasked());
                         return true;
                     }
                 }
@@ -732,6 +764,21 @@
             return true;
         }
 
+        private void updateDragStatus(int eventAction) {
+            switch (eventAction) {
+                case MotionEvent.ACTION_DOWN:
+                case MotionEvent.ACTION_UP:
+                case MotionEvent.ACTION_CANCEL: {
+                    mIsDragging = false;
+                    break;
+                }
+                case MotionEvent.ACTION_MOVE: {
+                    mIsDragging = true;
+                    break;
+                }
+            }
+        }
+
         /**
          * Perform a task size toggle on release of the double-tap, assuming no drag event
          * was handled during the double-tap.
@@ -856,6 +903,10 @@
      *
      * @param relevantDecor the window decoration of the focused task's caption. This method only
      *                      handles motion events outside this caption's bounds.
+     * TODO(b/349135068): Outside-touch detection no longer works with the
+     *  enableAdditionalWindowsAboveStatusBar flag enabled. This
+     *  will be fixed once we can add FLAG_WATCH_OUTSIDE_TOUCH to relevant menus,
+     *  at which point, all EventReceivers and external touch logic should be removed.
      */
     private void handleEventOutsideCaption(MotionEvent ev,
             DesktopModeWindowDecoration relevantDecor) {
@@ -908,9 +959,10 @@
                     dragFromStatusBarAllowed = windowingMode == WINDOWING_MODE_FULLSCREEN
                             || windowingMode == WINDOWING_MODE_MULTI_WINDOW;
                 }
-
-                if (dragFromStatusBarAllowed
-                        && relevantDecor.checkTouchEventInFocusedCaptionHandle(ev)) {
+                final boolean shouldStartTransitionDrag =
+                        relevantDecor.checkTouchEventInFocusedCaptionHandle(ev)
+                        || Flags.enableAdditionalWindowsAboveStatusBar();
+                if (dragFromStatusBarAllowed && shouldStartTransitionDrag) {
                     mTransitionDragActive = true;
                 }
                 break;
@@ -924,8 +976,15 @@
                         // Though this isn't a hover event, we need to update handle's hover state
                         // as it likely will change.
                         relevantDecor.updateHoverAndPressStatus(ev);
-                        mDesktopTasksController.onDragPositioningEndThroughStatusBar(
+                        DesktopModeVisualIndicator.IndicatorType resultType =
+                                mDesktopTasksController.onDragPositioningEndThroughStatusBar(
                                 new PointF(ev.getRawX(), ev.getRawY()), relevantDecor.mTaskInfo);
+                        // If we are entering split select, handle will no longer be visible and
+                        // should not be receiving any input.
+                        if (resultType == TO_SPLIT_LEFT_INDICATOR
+                                || resultType != TO_SPLIT_RIGHT_INDICATOR) {
+                            relevantDecor.disposeStatusBarInputLayer();
+                        }
                         mMoveToDesktopAnimator = null;
                         return;
                     } else {
@@ -937,7 +996,6 @@
                 relevantDecor.checkTouchEvent(ev);
                 break;
             }
-
             case ACTION_MOVE: {
                 if (relevantDecor == null) {
                     return;
@@ -1097,6 +1155,7 @@
                 mDesktopModeWindowDecorFactory.create(
                         mContext,
                         mDisplayController,
+                        mSplitScreenController,
                         mTaskOrganizer,
                         taskInfo,
                         taskSurface,
@@ -1139,7 +1198,9 @@
         windowDecoration.setDragDetector(touchEventListener.mDragDetector);
         windowDecoration.relayout(taskInfo, startT, finishT,
                 false /* applyStartTransactionOnDraw */, false /* shouldSetTaskPositionAndCrop */);
-        incrementEventReceiverTasks(taskInfo.displayId);
+        if (!Flags.enableAdditionalWindowsAboveStatusBar()) {
+            incrementEventReceiverTasks(taskInfo.displayId);
+        }
     }
 
     private RunningTaskInfo getOtherSplitTask(int taskId) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
index 7988983..529def7 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
@@ -24,6 +24,7 @@
 import static android.view.MotionEvent.ACTION_UP;
 
 import static com.android.launcher3.icons.BaseIconFactory.MODE_DEFAULT;
+import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT;
 import static com.android.wm.shell.windowdecor.DragResizeWindowGeometry.getFineResizeCornerSize;
 import static com.android.wm.shell.windowdecor.DragResizeWindowGeometry.getLargeResizeCornerSize;
 import static com.android.wm.shell.windowdecor.DragResizeWindowGeometry.getResizeEdgeHandleSize;
@@ -101,6 +102,7 @@
     private final @ShellBackgroundThread ShellExecutor mBgExecutor;
     private final Choreographer mChoreographer;
     private final SyncTransactionQueue mSyncQueue;
+    private final SplitScreenController mSplitScreenController;
 
     private WindowDecorationViewHolder mWindowDecorViewHolder;
     private View.OnClickListener mOnCaptionButtonClickListener;
@@ -151,6 +153,7 @@
     DesktopModeWindowDecoration(
             Context context,
             DisplayController displayController,
+            SplitScreenController splitScreenController,
             ShellTaskOrganizer taskOrganizer,
             ActivityManager.RunningTaskInfo taskInfo,
             SurfaceControl taskSurface,
@@ -159,16 +162,18 @@
             Choreographer choreographer,
             SyncTransactionQueue syncQueue,
             RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer) {
-        this (context, displayController, taskOrganizer, taskInfo, taskSurface,
-                handler, bgExecutor, choreographer, syncQueue, rootTaskDisplayAreaOrganizer,
-                SurfaceControl.Builder::new, SurfaceControl.Transaction::new,
-                WindowContainerTransaction::new, SurfaceControl::new,
-                new SurfaceControlViewHostFactory() {}, DefaultMaximizeMenuFactory.INSTANCE);
+        this (context, displayController, splitScreenController, taskOrganizer, taskInfo,
+                taskSurface, handler, bgExecutor, choreographer, syncQueue,
+                rootTaskDisplayAreaOrganizer, SurfaceControl.Builder::new,
+                SurfaceControl.Transaction::new,  WindowContainerTransaction::new,
+                SurfaceControl::new, new SurfaceControlViewHostFactory() {},
+                DefaultMaximizeMenuFactory.INSTANCE);
     }
 
     DesktopModeWindowDecoration(
             Context context,
             DisplayController displayController,
+            SplitScreenController splitScreenController,
             ShellTaskOrganizer taskOrganizer,
             ActivityManager.RunningTaskInfo taskInfo,
             SurfaceControl taskSurface,
@@ -187,6 +192,7 @@
                 surfaceControlBuilderSupplier, surfaceControlTransactionSupplier,
                 windowContainerTransactionSupplier, surfaceControlSupplier,
                 surfaceControlViewHostFactory);
+        mSplitScreenController = splitScreenController;
         mHandler = handler;
         mBgExecutor = bgExecutor;
         mChoreographer = choreographer;
@@ -367,27 +373,41 @@
         if (mResult.mRootView == null) {
             // This means something blocks the window decor from showing, e.g. the task is hidden.
             // Nothing is set up in this case including the decoration surface.
+            disposeStatusBarInputLayer();
             Trace.endSection(); // DesktopModeWindowDecoration#updateRelayoutParamsAndSurfaces
             return;
         }
 
         if (oldRootView != mResult.mRootView) {
+            disposeStatusBarInputLayer();
             mWindowDecorViewHolder = createViewHolder();
         }
         Trace.beginSection("DesktopModeWindowDecoration#relayout-binding");
-        mWindowDecorViewHolder.bindData(mTaskInfo);
+
+        final Point position = new Point();
+        if (isAppHandle(mWindowDecorViewHolder)) {
+            position.set(determineHandlePosition());
+        }
+        mWindowDecorViewHolder.bindData(mTaskInfo,
+                position,
+                mResult.mCaptionWidth,
+                mResult.mCaptionHeight,
+                isCaptionVisible());
         Trace.endSection();
 
         if (!mTaskInfo.isFocused) {
             closeHandleMenu();
             closeMaximizeMenu();
         }
-
         updateDragResizeListener(oldDecorationSurface);
         updateMaximizeMenu(startT);
         Trace.endSection(); // DesktopModeWindowDecoration#updateRelayoutParamsAndSurfaces
     }
 
+    private boolean isCaptionVisible() {
+        return mTaskInfo.isVisible && mIsCaptionVisible;
+    }
+
     private void setCapturedLink(Uri capturedLink, long timeStamp) {
         if (capturedLink == null
                 || (mCapturedLink != null && mCapturedLink.mTimeStamp == timeStamp)) {
@@ -469,12 +489,42 @@
         }
     }
 
+    private Point determineHandlePosition() {
+        final Point position = new Point(mResult.mCaptionX, 0);
+        if (mSplitScreenController.getSplitPosition(mTaskInfo.taskId)
+                == SPLIT_POSITION_BOTTOM_OR_RIGHT
+                && mDisplayController.getDisplayLayout(mTaskInfo.displayId).isLandscape()
+        ) {
+            // If this is the right split task, add left stage's width.
+            final Rect leftStageBounds = new Rect();
+            mSplitScreenController.getStageBounds(leftStageBounds, new Rect());
+            position.x += leftStageBounds.width();
+        }
+        return position;
+    }
+
+    /**
+     * Dispose of the view used to forward inputs in status bar region. Intended to be
+     * used any time handle is no longer visible.
+     */
+    void disposeStatusBarInputLayer() {
+        if (!isAppHandle(mWindowDecorViewHolder)
+                || !Flags.enableAdditionalWindowsAboveStatusBar()) {
+            return;
+        }
+        ((AppHandleViewHolder) mWindowDecorViewHolder).disposeStatusBarInputLayer();
+    }
+
     private WindowDecorationViewHolder createViewHolder() {
         if (mRelayoutParams.mLayoutResId == R.layout.desktop_mode_app_handle) {
             return new AppHandleViewHolder(
                     mResult.mRootView,
                     mOnCaptionTouchListener,
-                    mOnCaptionButtonClickListener
+                    mOnCaptionButtonClickListener,
+                    (v, event) -> {
+                        updateHoverAndPressStatus(event);
+                        return true;
+                    }
             );
         } else if (mRelayoutParams.mLayoutResId
                 == R.layout.desktop_mode_app_header) {
@@ -497,6 +547,10 @@
         throw new IllegalArgumentException("Unexpected layout resource id");
     }
 
+    private boolean isAppHandle(WindowDecorationViewHolder viewHolder) {
+        return viewHolder instanceof AppHandleViewHolder;
+    }
+
     @VisibleForTesting
     static void updateRelayoutParams(
             RelayoutParams relayoutParams,
@@ -965,10 +1019,17 @@
      * @return {@code true} if event is inside caption handle view, {@code false} if not
      */
     boolean checkTouchEventInFocusedCaptionHandle(MotionEvent ev) {
-        if (isHandleMenuActive() || !(mWindowDecorViewHolder
-                instanceof AppHandleViewHolder)) {
+        if (isHandleMenuActive() || !isAppHandle(mWindowDecorViewHolder)
+                || Flags.enableAdditionalWindowsAboveStatusBar()) {
             return false;
         }
+        // The status bar input layer can only receive input in handle coordinates to begin with,
+        // so checking coordinates is unnecessary as input is always within handle bounds.
+        if (isAppHandle(mWindowDecorViewHolder)
+                && Flags.enableAdditionalWindowsAboveStatusBar()
+                && isCaptionVisible()) {
+            return true;
+        }
 
         return checkTouchEventInCaption(ev);
     }
@@ -1002,7 +1063,7 @@
      * @param ev the MotionEvent to compare
      */
     void checkTouchEvent(MotionEvent ev) {
-        if (mResult.mRootView == null) return;
+        if (mResult.mRootView == null || Flags.enableAdditionalWindowsAboveStatusBar()) return;
         final View caption = mResult.mRootView.findViewById(R.id.desktop_mode_caption);
         final View handle = caption.findViewById(R.id.caption_handle);
         final boolean inHandle = !isHandleMenuActive()
@@ -1024,7 +1085,7 @@
      * @param ev the MotionEvent to compare against.
      */
     void updateHoverAndPressStatus(MotionEvent ev) {
-        if (mResult.mRootView == null) return;
+        if (mResult.mRootView == null || Flags.enableAdditionalWindowsAboveStatusBar()) return;
         final View handle = mResult.mRootView.findViewById(R.id.caption_handle);
         final boolean inHandle = !isHandleMenuActive()
                 && checkTouchEventInFocusedCaptionHandle(ev);
@@ -1034,15 +1095,11 @@
         // We want handle to remain pressed if the pointer moves outside of it during a drag.
         handle.setPressed((inHandle && action == ACTION_DOWN)
                 || (handle.isPressed() && action != ACTION_UP && action != ACTION_CANCEL));
-        if (isHandleMenuActive() && !isHandleMenuAboveStatusBar()) {
+        if (isHandleMenuActive()) {
             mHandleMenu.checkMotionEvent(ev);
         }
     }
 
-    private boolean isHandleMenuAboveStatusBar() {
-        return Flags.enableAdditionalWindowsAboveStatusBar() && !mTaskInfo.isFreeform();
-    }
-
     private boolean pointInView(View v, float x, float y) {
         return v != null && v.getLeft() <= x && v.getRight() >= x
                 && v.getTop() <= y && v.getBottom() >= y;
@@ -1054,6 +1111,7 @@
         closeHandleMenu();
         mExclusionRegionListener.onExclusionRegionDismissed(mTaskInfo.taskId);
         disposeResizeVeil();
+        disposeStatusBarInputLayer();
         clearCurrentViewHostRunnable();
         super.close();
     }
@@ -1153,6 +1211,7 @@
         DesktopModeWindowDecoration create(
                 Context context,
                 DisplayController displayController,
+                SplitScreenController splitScreenController,
                 ShellTaskOrganizer taskOrganizer,
                 ActivityManager.RunningTaskInfo taskInfo,
                 SurfaceControl taskSurface,
@@ -1164,6 +1223,7 @@
             return new DesktopModeWindowDecoration(
                     context,
                     displayController,
+                    splitScreenController,
                     taskOrganizer,
                     taskInfo,
                     taskSurface,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java
index d212f21..a691f59 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java
@@ -139,7 +139,7 @@
     private SurfaceControlViewHost mViewHost;
     private Configuration mWindowDecorConfig;
     TaskDragResizer mTaskDragResizer;
-    private boolean mIsCaptionVisible;
+    boolean mIsCaptionVisible;
 
     /** The most recent set of insets applied to this window decoration. */
     private WindowDecorationInsets mWindowDecorationInsets;
@@ -508,6 +508,8 @@
         mTaskDragResizer = taskDragResizer;
     }
 
+    // TODO(b/346441962): Move these three methods closer to implementing or View-level classes to
+    //  keep implementation details more encapsulated.
     private void setCaptionVisibility(View rootView, boolean visible) {
         if (rootView == null) {
             return;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/additionalviewcontainer/AdditionalSystemViewContainer.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/additionalviewcontainer/AdditionalSystemViewContainer.kt
index 03d2421..6a354f1 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/additionalviewcontainer/AdditionalSystemViewContainer.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/additionalviewcontainer/AdditionalSystemViewContainer.kt
@@ -30,18 +30,22 @@
  */
 class AdditionalSystemViewContainer(
     private val context: Context,
-    layoutId: Int,
     taskId: Int,
     x: Int,
     y: Int,
     width: Int,
-    height: Int
+    height: Int,
+    layoutId: Int? = null
 ) : AdditionalViewContainer() {
     override val view: View
     val windowManager: WindowManager? = context.getSystemService(WindowManager::class.java)
 
     init {
-        view = LayoutInflater.from(context).inflate(layoutId, null)
+        if (layoutId != null) {
+            view = LayoutInflater.from(context).inflate(layoutId, null)
+        } else {
+            view = View(context)
+        }
         val lp = WindowManager.LayoutParams(
             width, height, x, y,
             WindowManager.LayoutParams.TYPE_STATUS_BAR_ADDITIONAL,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/AppHandleViewHolder.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/AppHandleViewHolder.kt
index 8d822c2..76dfe37 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/AppHandleViewHolder.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/AppHandleViewHolder.kt
@@ -20,37 +20,68 @@
 import android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM
 import android.content.res.ColorStateList
 import android.graphics.Color
+import android.graphics.Point
+import android.view.SurfaceControl
 import android.view.View
+import android.view.View.OnClickListener
+import android.view.View.OnHoverListener
 import android.view.WindowInsetsController.APPEARANCE_LIGHT_STATUS_BARS
+import android.view.WindowManager
 import android.widget.ImageButton
+import com.android.window.flags.Flags
 import com.android.wm.shell.R
 import com.android.wm.shell.animation.Interpolators
+import com.android.wm.shell.windowdecor.additionalviewcontainer.AdditionalSystemViewContainer
 
 /**
  * A desktop mode window decoration used when the window is in full "focus" (i.e. fullscreen/split).
  * It hosts a simple handle bar from which to initiate a drag motion to enter desktop mode.
  */
 internal class AppHandleViewHolder(
-        rootView: View,
-        onCaptionTouchListener: View.OnTouchListener,
-        onCaptionButtonClickListener: View.OnClickListener
+    rootView: View,
+    private val onCaptionTouchListener: View.OnTouchListener,
+    private val onCaptionButtonClickListener: OnClickListener,
+    private val onCaptionHoverListener: OnHoverListener,
 ) : WindowDecorationViewHolder(rootView) {
 
     companion object {
         private const val CAPTION_HANDLE_ANIMATION_DURATION: Long = 100
     }
-
+    private lateinit var taskInfo: RunningTaskInfo
+    private val windowManager = context.getSystemService(WindowManager::class.java)
     private val captionView: View = rootView.requireViewById(R.id.desktop_mode_caption)
     private val captionHandle: ImageButton = rootView.requireViewById(R.id.caption_handle)
 
+    // An invisible View that takes up the same coordinates as captionHandle but is layered
+    // above the status bar. The purpose of this View is to receive input intended for
+    // captionHandle.
+    private var statusBarInputLayer: AdditionalSystemViewContainer? = null
+
     init {
         captionView.setOnTouchListener(onCaptionTouchListener)
         captionHandle.setOnTouchListener(onCaptionTouchListener)
         captionHandle.setOnClickListener(onCaptionButtonClickListener)
+        captionHandle.setOnHoverListener(onCaptionHoverListener)
     }
 
-    override fun bindData(taskInfo: RunningTaskInfo) {
+    override fun bindData(
+        taskInfo: RunningTaskInfo,
+        position: Point,
+        width: Int,
+        height: Int,
+        isCaptionVisible: Boolean
+    ) {
         captionHandle.imageTintList = ColorStateList.valueOf(getCaptionHandleBarColor(taskInfo))
+        this.taskInfo = taskInfo
+        if (!isCaptionVisible && hasStatusBarInputLayer()) {
+            disposeStatusBarInputLayer()
+            return
+        }
+        if (hasStatusBarInputLayer()) {
+            updateStatusBarInputLayer(position)
+        } else {
+            createStatusBarInputLayer(position, width, height)
+        }
     }
 
     override fun onHandleMenuOpened() {
@@ -61,6 +92,45 @@
         animateCaptionHandleAlpha(startValue = 0f, endValue = 1f)
     }
 
+    private fun createStatusBarInputLayer(handlePosition: Point,
+                                          handleWidth: Int,
+                                          handleHeight: Int) {
+        if (!Flags.enableAdditionalWindowsAboveStatusBar()) return
+        statusBarInputLayer = AdditionalSystemViewContainer(context, taskInfo.taskId,
+            handlePosition.x, handlePosition.y, handleWidth, handleHeight)
+        val view = statusBarInputLayer?.view
+        val lp = view?.layoutParams as WindowManager.LayoutParams
+        lp.title = "Handle Input Layer of task " + taskInfo.taskId
+        lp.setTrustedOverlay()
+        // Make this window a spy window to enable it to pilfer pointers from the system-wide
+        // gesture listener that receives events before window. This is to prevent notification
+        // shade gesture when we swipe down to enter desktop.
+        lp.inputFeatures = WindowManager.LayoutParams.INPUT_FEATURE_SPY
+        view.id = R.id.caption_handle
+        view.setOnClickListener(onCaptionButtonClickListener)
+        view.setOnTouchListener(onCaptionTouchListener)
+        view.setOnHoverListener(onCaptionHoverListener)
+        windowManager.updateViewLayout(view, lp)
+    }
+
+    private fun updateStatusBarInputLayer(globalPosition: Point) {
+        statusBarInputLayer?.setPosition(SurfaceControl.Transaction(), globalPosition.x.toFloat(),
+            globalPosition.y.toFloat()) ?: return
+    }
+
+    private fun hasStatusBarInputLayer(): Boolean {
+        return statusBarInputLayer != null
+    }
+
+    /**
+     * Remove the input layer from [WindowManager]. Should be used when caption handle
+     * is not visible.
+     */
+    fun disposeStatusBarInputLayer() {
+        statusBarInputLayer?.releaseView()
+        statusBarInputLayer = null
+    }
+
     private fun getCaptionHandleBarColor(taskInfo: RunningTaskInfo): Int {
         return if (shouldUseLightCaptionColors(taskInfo)) {
             context.getColor(R.color.desktop_mode_caption_handle_bar_light)
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/AppHeaderViewHolder.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/AppHeaderViewHolder.kt
index 46127b1..b704d9c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/AppHeaderViewHolder.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/AppHeaderViewHolder.kt
@@ -21,6 +21,7 @@
 import android.content.res.Configuration
 import android.graphics.Bitmap
 import android.graphics.Color
+import android.graphics.Point
 import android.graphics.drawable.LayerDrawable
 import android.graphics.drawable.RippleDrawable
 import android.graphics.drawable.ShapeDrawable
@@ -136,7 +137,13 @@
                 onMaximizeHoverAnimationFinishedListener
     }
 
-    override fun bindData(taskInfo: RunningTaskInfo) {
+    override fun bindData(
+        taskInfo: RunningTaskInfo,
+        position: Point,
+        width: Int,
+        height: Int,
+        isCaptionVisible: Boolean
+    ) {
         if (Flags.enableThemedAppHeaders()) {
             bindDataWithThemedHeaders(taskInfo)
         } else {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/WindowDecorationViewHolder.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/WindowDecorationViewHolder.kt
index 5ae8d25..2341b09 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/WindowDecorationViewHolder.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/WindowDecorationViewHolder.kt
@@ -17,6 +17,7 @@
 
 import android.app.ActivityManager.RunningTaskInfo
 import android.content.Context
+import android.graphics.Point
 import android.view.View
 
 /**
@@ -30,7 +31,13 @@
    * A signal to the view holder that new data is available and that the views should be updated to
    * reflect it.
    */
-  abstract fun bindData(taskInfo: RunningTaskInfo)
+  abstract fun bindData(
+    taskInfo: RunningTaskInfo,
+    position: Point,
+    width: Int,
+    height: Int,
+    isCaptionVisible: Boolean
+  )
 
   /** Callback when the handle menu is opened. */
   abstract fun onHandleMenuOpened()
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt
index ae00c3e..aeae0be 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt
@@ -30,6 +30,7 @@
 import android.hardware.display.VirtualDisplay
 import android.hardware.input.InputManager
 import android.os.Handler
+import android.platform.test.annotations.DisableFlags
 import android.platform.test.annotations.EnableFlags
 import android.platform.test.annotations.RequiresFlagsEnabled
 import android.platform.test.flag.junit.CheckFlagsRule
@@ -72,6 +73,7 @@
 import com.android.wm.shell.desktopmode.DesktopTasksController.SnapPosition
 import com.android.wm.shell.freeform.FreeformTaskTransitionStarter
 import com.android.wm.shell.shared.desktopmode.DesktopModeStatus
+import com.android.wm.shell.splitscreen.SplitScreenController
 import com.android.wm.shell.sysui.KeyguardChangeListener
 import com.android.wm.shell.sysui.ShellCommandHandler
 import com.android.wm.shell.sysui.ShellController
@@ -79,8 +81,6 @@
 import com.android.wm.shell.transition.Transitions
 import com.android.wm.shell.windowdecor.DesktopModeWindowDecorViewModel.DesktopModeOnInsetsChangedListener
 import com.android.wm.shell.windowdecor.common.OnTaskActionClickListener
-import java.util.Optional
-import java.util.function.Supplier
 import org.junit.Assert.assertEquals
 import org.junit.Before
 import org.junit.Rule
@@ -100,6 +100,8 @@
 import org.mockito.kotlin.spy
 import org.mockito.kotlin.whenever
 import org.mockito.quality.Strictness
+import java.util.Optional
+import java.util.function.Supplier
 
 /**
  * Tests of [DesktopModeWindowDecorViewModel]
@@ -123,6 +125,7 @@
     @Mock private lateinit var mockMainChoreographer: Choreographer
     @Mock private lateinit var mockTaskOrganizer: ShellTaskOrganizer
     @Mock private lateinit var mockDisplayController: DisplayController
+    @Mock private lateinit var mockSplitScreenController: SplitScreenController
     @Mock private lateinit var mockDisplayLayout: DisplayLayout
     @Mock private lateinit var displayInsetsController: DisplayInsetsController
     @Mock private lateinit var mockSyncQueue: SyncTransactionQueue
@@ -174,7 +177,7 @@
                 mockRootTaskDisplayAreaOrganizer,
             windowDecorByTaskIdSpy, mockInteractionJankMonitor
         )
-
+        desktopModeWindowDecorViewModel.setSplitScreenController(mockSplitScreenController)
         whenever(mockDisplayController.getDisplayLayout(any())).thenReturn(mockDisplayLayout)
         whenever(mockDisplayLayout.stableInsets()).thenReturn(STABLE_INSETS)
         whenever(mockInputMonitorFactory.create(any(), any())).thenReturn(mockInputMonitor)
@@ -207,6 +210,7 @@
         verify(mockDesktopModeWindowDecorFactory).create(
                 mContext,
                 mockDisplayController,
+                mockSplitScreenController,
                 mockTaskOrganizer,
                 task,
                 taskSurface,
@@ -232,6 +236,7 @@
         verify(mockDesktopModeWindowDecorFactory, never()).create(
                 mContext,
                 mockDisplayController,
+                mockSplitScreenController,
                 mockTaskOrganizer,
                 task,
                 taskSurface,
@@ -248,6 +253,7 @@
         verify(mockDesktopModeWindowDecorFactory, times(1)).create(
                 mContext,
                 mockDisplayController,
+                mockSplitScreenController,
                 mockTaskOrganizer,
                 task,
                 taskSurface,
@@ -260,6 +266,7 @@
     }
 
     @Test
+    @DisableFlags(Flags.FLAG_ENABLE_ADDITIONAL_WINDOWS_ABOVE_STATUS_BAR)
     fun testCreateAndDisposeEventReceiver() {
         val task = createTask(windowingMode = WINDOWING_MODE_FREEFORM)
         setUpMockDecorationForTask(task)
@@ -272,6 +279,7 @@
     }
 
     @Test
+    @DisableFlags(Flags.FLAG_ENABLE_ADDITIONAL_WINDOWS_ABOVE_STATUS_BAR)
     fun testEventReceiversOnMultipleDisplays() {
         val secondaryDisplay = createVirtualDisplay() ?: return
         val secondaryDisplayId = secondaryDisplay.display.displayId
@@ -350,7 +358,8 @@
         onTaskChanging(task)
 
         verify(mockDesktopModeWindowDecorFactory, never())
-                .create(any(), any(), any(), eq(task), any(), any(), any(), any(), any(), any())
+                .create(any(), any(), any(), any(), eq(task), any(), any(), any(), any(), any(),
+                    any())
     }
 
     @Test
@@ -371,7 +380,8 @@
 
             onTaskOpening(task)
             verify(mockDesktopModeWindowDecorFactory)
-                    .create(any(), any(), any(), eq(task), any(), any(), any(), any(), any(), any())
+                    .create(any(), any(), any(), any(), eq(task), any(), any(), any(), any(),
+                        any(), any())
         } finally {
             mockitoSession.finishMocking()
         }
@@ -388,7 +398,8 @@
         onTaskOpening(task)
 
         verify(mockDesktopModeWindowDecorFactory, never())
-                .create(any(), any(), any(), eq(task), any(), any(), any(), any(), any(), any())
+                .create(any(), any(), any(), any(), eq(task), any(), any(), any(), any(), any(),
+                    any())
     }
 
     @Test
@@ -405,7 +416,8 @@
         onTaskOpening(task)
 
         verify(mockDesktopModeWindowDecorFactory, never())
-                .create(any(), any(), any(), eq(task), any(), any(), any(), any(), any(), any())
+                .create(any(), any(), any(), any(), eq(task), any(), any(), any(), any(),
+                    any(), any())
     }
 
     @Test
@@ -502,7 +514,8 @@
 
             onTaskOpening(task)
             verify(mockDesktopModeWindowDecorFactory, never())
-                .create(any(), any(), any(), eq(task), any(), any(), any(), any(), any(), any())
+                .create(any(), any(), any(), any(), eq(task), any(), any(), any(), any(), any(),
+                    any())
         } finally {
             mockitoSession.finishMocking()
         }
@@ -526,7 +539,8 @@
 
             onTaskOpening(task)
             verify(mockDesktopModeWindowDecorFactory)
-                .create(any(), any(), any(), eq(task), any(), any(), any(), any(), any(), any())
+                .create(any(), any(), any(), any(), eq(task), any(), any(), any(), any(), any(),
+                    any())
         } finally {
             mockitoSession.finishMocking()
         }
@@ -549,7 +563,8 @@
 
             onTaskOpening(task)
             verify(mockDesktopModeWindowDecorFactory)
-                .create(any(), any(), any(), eq(task), any(), any(), any(), any(), any(), any())
+                .create(any(), any(), any(), any(), eq(task), any(), any(), any(), any(), any(),
+                any())
         } finally {
             mockitoSession.finishMocking()
         }
@@ -688,7 +703,7 @@
         val decoration = mock(DesktopModeWindowDecoration::class.java)
         whenever(
             mockDesktopModeWindowDecorFactory.create(
-                any(), any(), any(), eq(task), any(), any(), any(), any(), any(), any())
+                any(), any(), any(), any(), eq(task), any(), any(), any(), any(), any(), any())
         ).thenReturn(decoration)
         decoration.mTaskInfo = task
         whenever(decoration.isFocused).thenReturn(task.isFocused)
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java
index 2c19fdc..412fef3 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java
@@ -87,6 +87,7 @@
 import com.android.wm.shell.common.ShellExecutor;
 import com.android.wm.shell.common.SyncTransactionQueue;
 import com.android.wm.shell.shared.desktopmode.DesktopModeStatus;
+import com.android.wm.shell.splitscreen.SplitScreenController;
 import com.android.wm.shell.windowdecor.WindowDecoration.RelayoutParams;
 import com.android.wm.shell.windowdecor.common.OnTaskActionClickListener;
 
@@ -129,6 +130,8 @@
     @Mock
     private DisplayController mMockDisplayController;
     @Mock
+    private SplitScreenController mMockSplitScreenController;
+    @Mock
     private ShellTaskOrganizer mMockShellTaskOrganizer;
     @Mock
     private Choreographer mMockChoreographer;
@@ -395,6 +398,7 @@
     }
 
     @Test
+    @DisableFlags(Flags.FLAG_ENABLE_ADDITIONAL_WINDOWS_ABOVE_STATUS_BAR)
     public void relayout_fullscreenTask_appliesTransactionImmediately() {
         final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(/* visible= */ true);
         final DesktopModeWindowDecoration spyWindowDecor = spy(createWindowDecoration(taskInfo));
@@ -421,6 +425,7 @@
     }
 
     @Test
+    @DisableFlags(Flags.FLAG_ENABLE_ADDITIONAL_WINDOWS_ABOVE_STATUS_BAR)
     public void relayout_fullscreenTask_doesNotCreateViewHostImmediately() {
         final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(/* visible= */ true);
         final DesktopModeWindowDecoration spyWindowDecor = spy(createWindowDecoration(taskInfo));
@@ -432,6 +437,7 @@
     }
 
     @Test
+    @DisableFlags(Flags.FLAG_ENABLE_ADDITIONAL_WINDOWS_ABOVE_STATUS_BAR)
     public void relayout_fullscreenTask_postsViewHostCreation() {
         final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(/* visible= */ true);
         final DesktopModeWindowDecoration spyWindowDecor = spy(createWindowDecoration(taskInfo));
@@ -460,6 +466,7 @@
     }
 
     @Test
+    @DisableFlags(Flags.FLAG_ENABLE_ADDITIONAL_WINDOWS_ABOVE_STATUS_BAR)
     public void relayout_removesExistingHandlerCallback() {
         final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(/* visible= */ true);
         final DesktopModeWindowDecoration spyWindowDecor = spy(createWindowDecoration(taskInfo));
@@ -474,6 +481,7 @@
     }
 
     @Test
+    @DisableFlags(Flags.FLAG_ENABLE_ADDITIONAL_WINDOWS_ABOVE_STATUS_BAR)
     public void close_removesExistingHandlerCallback() {
         final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(/* visible= */ true);
         final DesktopModeWindowDecoration spyWindowDecor = spy(createWindowDecoration(taskInfo));
@@ -659,9 +667,9 @@
             ActivityManager.RunningTaskInfo taskInfo,
             MaximizeMenuFactory maximizeMenuFactory) {
         final DesktopModeWindowDecoration windowDecor = new DesktopModeWindowDecoration(mContext,
-                mMockDisplayController, mMockShellTaskOrganizer, taskInfo, mMockSurfaceControl,
-                mMockHandler, mBgExecutor, mMockChoreographer, mMockSyncQueue,
-                mMockRootTaskDisplayAreaOrganizer,
+                mMockDisplayController, mMockSplitScreenController, mMockShellTaskOrganizer,
+                taskInfo, mMockSurfaceControl, mMockHandler, mBgExecutor, mMockChoreographer,
+                mMockSyncQueue, mMockRootTaskDisplayAreaOrganizer,
                 SurfaceControl.Builder::new, mMockTransactionSupplier,
                 WindowContainerTransaction::new, SurfaceControl::new,
                 mMockSurfaceControlViewHostFactory, maximizeMenuFactory);
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/additionalviewcontainer/AdditionalSystemViewContainerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/additionalviewcontainer/AdditionalSystemViewContainerTest.kt
index d3e996b..3b49055 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/additionalviewcontainer/AdditionalSystemViewContainerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/additionalviewcontainer/AdditionalSystemViewContainerTest.kt
@@ -68,12 +68,12 @@
     fun testReleaseView_ViewRemoved() {
         viewContainer = AdditionalSystemViewContainer(
             mockContext,
-            R.layout.desktop_mode_window_decor_handle_menu,
             TASK_ID,
             X,
             Y,
             WIDTH,
-            HEIGHT
+            HEIGHT,
+            R.layout.desktop_mode_window_decor_handle_menu
         )
         verify(mockWindowManager).addView(eq(mockView), any())
         viewContainer.releaseView()