From a9b24fb2b1152148819c6240454e633af064f708 Mon Sep 17 00:00:00 2001 From: Garfield Tan Date: Fri, 23 Jun 2023 14:48:30 -0700 Subject: [PATCH] Use spy windows to resize tasks This CL does a few things to achieve the goal: 1. It converts the input windows of decor container layers to spy windows, so that input windows behind them can receive input events at the same time. This is what allows the close button in the caption to receive mouse clicks even if the mouse clicks in the intersected area of the close button and the square top-right touch resize handle. 2. It adds task input sinks behind decor container layers, so that motion events received by resize handlers out of task windows won't be dispatched to windows behind the resize handlers. I chose to put the task input sinks under the decor container layers so that the logic can be mostly limited inside DragResizeInputListener. 3. Resize handlers pilfer events when they decide to handle resize gestures. Windows that received the same gesture will receive a cancelled event. There are a few remaining issues: 1. We update mouse cursors whenever the resize handlers receive hover events. We should avoid updating them when they aren't supposed to to resize task windows if the resize is started at the hover events, so windows behind the resize handlers have chances to update the mouse cursor shape. 2. The mouse cursor shape are updated to double arrows if the mouse hovers in corner areas that are slightly over task radii inward to one side of the task bounds, and less than corner sizes inward to an adjacent side of the task bounds. However this place is completely inside the task bounds, so we shouldn't resize the task at all with mice. This CL also changes the CtrlType annotation to be flags. Bug: 266866903 Test: Close buttons in captions be clicked when the mouse cursor is close to the corner. Change-Id: I28215f1dc51bfaaccfd5fa4856186f4a6f0e4925 --- .../windowdecor/CaptionWindowDecoration.java | 4 +- .../DesktopModeWindowDecoration.java | 4 +- .../windowdecor/DragPositioningCallback.java | 4 +- .../windowdecor/DragResizeInputListener.java | 92 ++++++++++++++++++- .../shell/windowdecor/WindowDecoration.java | 23 ++++- 5 files changed, 115 insertions(+), 12 deletions(-) diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java index b217bd39a4466..ce8191067ae99 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java @@ -145,7 +145,9 @@ public class CaptionWindowDecoration extends WindowDecoration mSurfaceControlTransactionSupplier; private final int mDisplayId; private final BaseIWindow mFakeWindow; @@ -69,6 +75,10 @@ class DragResizeInputListener implements AutoCloseable { private final TaskResizeInputEventReceiver mInputEventReceiver; private final DragPositioningCallback mCallback; + private final SurfaceControl mInputSinkSurface; + private final BaseIWindow mFakeSinkWindow; + private final InputChannel mSinkInputChannel; + private int mTaskWidth; private int mTaskHeight; private int mResizeHandleThickness; @@ -90,15 +100,18 @@ class DragResizeInputListener implements AutoCloseable { int displayId, int taskCornerRadius, SurfaceControl decorationSurface, - DragPositioningCallback callback) { + DragPositioningCallback callback, + Supplier surfaceControlBuilderSupplier, + Supplier surfaceControlTransactionSupplier) { mInputManager = context.getSystemService(InputManager.class); mHandler = handler; mChoreographer = choreographer; + mSurfaceControlTransactionSupplier = surfaceControlTransactionSupplier; mDisplayId = displayId; mTaskCornerRadius = taskCornerRadius; mDecorationSurface = decorationSurface; - // Use a fake window as the backing surface is a container layer and we don't want to create - // a buffer layer for it so we can't use ViewRootImpl. + // Use a fake window as the backing surface is a container layer, and we don't want to + // create a buffer layer for it, so we can't use ViewRootImpl. mFakeWindow = new BaseIWindow(); mFakeWindow.setSession(mWindowSession); mFocusGrantToken = new Binder(); @@ -111,7 +124,7 @@ class DragResizeInputListener implements AutoCloseable { null /* hostInputToken */, FLAG_NOT_FOCUSABLE, PRIVATE_FLAG_TRUSTED_OVERLAY, - 0 /* inputFeatures */, + INPUT_FEATURE_SPY, TYPE_APPLICATION, null /* windowToken */, mFocusGrantToken, @@ -126,6 +139,35 @@ class DragResizeInputListener implements AutoCloseable { mCallback = callback; mDragDetector = new DragDetector(mInputEventReceiver); mDragDetector.setTouchSlop(ViewConfiguration.get(context).getScaledTouchSlop()); + + mInputSinkSurface = surfaceControlBuilderSupplier.get() + .setName("TaskInputSink of " + decorationSurface) + .setContainerLayer() + .setParent(mDecorationSurface) + .build(); + mSurfaceControlTransactionSupplier.get() + .setLayer(mInputSinkSurface, WindowDecoration.INPUT_SINK_Z_ORDER) + .show(mInputSinkSurface) + .apply(); + mFakeSinkWindow = new BaseIWindow(); + mSinkInputChannel = new InputChannel(); + try { + mWindowSession.grantInputChannel( + mDisplayId, + mInputSinkSurface, + mFakeSinkWindow, + null /* hostInputToken */, + FLAG_NOT_FOCUSABLE, + 0 /* privateFlags */, + INPUT_FEATURE_NO_INPUT_CHANNEL, + TYPE_INPUT_CONSUMER, + null /* windowToken */, + mFocusGrantToken, + "TaskInputSink of " + decorationSurface, + mSinkInputChannel); + } catch (RemoteException e) { + e.rethrowFromSystemServer(); + } } /** @@ -219,7 +261,35 @@ class DragResizeInputListener implements AutoCloseable { mDecorationSurface, FLAG_NOT_FOCUSABLE, PRIVATE_FLAG_TRUSTED_OVERLAY, - 0 /* inputFeatures */, + INPUT_FEATURE_SPY, + touchRegion); + } catch (RemoteException e) { + e.rethrowFromSystemServer(); + } + + mSurfaceControlTransactionSupplier.get() + .setWindowCrop(mInputSinkSurface, mTaskWidth, mTaskHeight) + .apply(); + // The touch region of the TaskInputSink should be the touch region of this + // DragResizeInputHandler minus the task bounds. Pilfering events isn't enough to prevent + // input windows from handling down events, which will bring tasks in the back to front. + // + // Note not the entire touch region responds to both mouse and touchscreen events. + // Therefore, in the region that only responds to one of them, it would be a no-op to + // perform a gesture in the other type of events. We currently only have a mouse-only region + // out of the task bounds, and due to the roughness of touchscreen events, it's not a severe + // issue. However, were there touchscreen-only a region out of the task bounds, mouse + // gestures will become no-op in that region, even though the mouse gestures may appear to + // be performed on the input window behind the resize handle. + touchRegion.op(0, 0, mTaskWidth, mTaskHeight, Region.Op.DIFFERENCE); + try { + mWindowSession.updateInputChannel( + mSinkInputChannel.getToken(), + mDisplayId, + mInputSinkSurface, + FLAG_NOT_FOCUSABLE, + 0 /* privateFlags */, + INPUT_FEATURE_NO_INPUT_CHANNEL, touchRegion); } catch (RemoteException e) { e.rethrowFromSystemServer(); @@ -248,6 +318,16 @@ class DragResizeInputListener implements AutoCloseable { } catch (RemoteException e) { e.rethrowFromSystemServer(); } + + mSinkInputChannel.dispose(); + try { + mWindowSession.remove(mFakeSinkWindow); + } catch (RemoteException e) { + e.rethrowFromSystemServer(); + } + mSurfaceControlTransactionSupplier.get() + .remove(mInputSinkSurface) + .apply(); } private class TaskResizeInputEventReceiver extends InputEventReceiver @@ -316,6 +396,8 @@ class DragResizeInputListener implements AutoCloseable { mShouldHandleEvents = isInResizeHandleBounds(x, y); } if (mShouldHandleEvents) { + mInputManager.pilferPointers(mInputChannel.getToken()); + mDragPointerId = e.getPointerId(0); float rawX = e.getRawX(0); float rawY = e.getRawY(0); 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 ddc7fef0599f5..0b0d9d5086f41 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 @@ -63,6 +63,24 @@ import java.util.function.Supplier; public abstract class WindowDecoration implements AutoCloseable { + /** + * The Z-order of {@link #mCaptionContainerSurface}. + *

+ * We use {@link #mDecorationContainerSurface} to define input window for task resizing; by + * layering it in front of {@link #mCaptionContainerSurface}, we can allow it to handle input + * prior to caption view itself, treating corner inputs as resize events rather than + * repositioning. + */ + static final int CAPTION_LAYER_Z_ORDER = -1; + /** + * The Z-order of the task input sink in {@link DragPositioningCallback}. + *

+ * This task input sink is used to prevent undesired dispatching of motion events out of task + * bounds; by layering it behind {@link #mCaptionContainerSurface}, we allow captions to handle + * input events first. + */ + static final int INPUT_SINK_Z_ORDER = -2; + /** * System-wide context. Only used to create context with overridden configurations. */ @@ -238,11 +256,8 @@ public abstract class WindowDecoration final int captionHeight = loadDimensionPixelSize(resources, params.mCaptionHeightId); final int captionWidth = taskBounds.width(); - // We use mDecorationContainerSurface to define input window for task resizing; by layering - // it in front of mCaptionContainerSurface, we can allow it to handle input prior to - // caption view itself, treating corner inputs as resize events rather than repositioning. startT.setWindowCrop(mCaptionContainerSurface, captionWidth, captionHeight) - .setLayer(mCaptionContainerSurface, -1) + .setLayer(mCaptionContainerSurface, CAPTION_LAYER_Z_ORDER) .show(mCaptionContainerSurface); if (ViewRootImpl.CAPTION_ON_SHELL) {