Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 66112fa6 authored by Jorge Gil's avatar Jorge Gil
Browse files

Require hold-to-drag for App Handle drags

Adds a holding period functionality to DragDetector, which requires a
hold within the slop region to be maintained for X amount of ms before
ACTION_MOVEs outside the slop are allowed (reported to the event
handler). This functionality is enabled for the App Handle's drag
detector behind a flag, and disable for every other drag detector
(header, resize listener).

Also modifies e2e test to check the type of input before entering
desktop with drag, and simulates a hold-to-drag when the input is from a
touchscreen.

Flag: com.android.window.flags.enable_hold_to_drag_app_handle
Bug: 356409496
Test: atest WMShellUnitTests; atest PlatformScenarioTests
Change-Id: Ib57be0ce8b63aaa17ecc57b70d1629ab88c69787
parent 6f62c8c1
Loading
Loading
Loading
Loading
+3 −2
Original line number Diff line number Diff line
@@ -44,6 +44,7 @@ import android.view.IWindowManager;
import android.view.MotionEvent;
import android.view.SurfaceControl;
import android.view.View;
import android.view.ViewConfiguration;
import android.window.DisplayAreaInfo;
import android.window.WindowContainerToken;
import android.window.WindowContainerTransaction;
@@ -310,7 +311,6 @@ public class CaptionWindowDecorViewModel implements WindowDecorViewModel {
                new CaptionTouchEventListener(taskInfo, taskPositioner);
        windowDecoration.setCaptionListeners(touchEventListener, touchEventListener);
        windowDecoration.setDragPositioningCallback(taskPositioner);
        windowDecoration.setDragDetector(touchEventListener.mDragDetector);
        windowDecoration.setTaskDragResizer(taskPositioner);
        windowDecoration.relayout(taskInfo, startT, finishT,
                false /* applyStartTransactionOnDraw */, false /* setTaskCropAndPosition */);
@@ -334,7 +334,8 @@ public class CaptionWindowDecorViewModel implements WindowDecorViewModel {
            mTaskId = taskInfo.taskId;
            mTaskToken = taskInfo.token;
            mDragPositioningCallback = dragPositioningCallback;
            mDragDetector = new DragDetector(this);
            mDragDetector = new DragDetector(this, 0 /* holdToDragMinDurationMs */,
                    ViewConfiguration.get(mContext).getScaledTouchSlop());
            mDisplayId = taskInfo.displayId;
        }

+0 −8
Original line number Diff line number Diff line
@@ -74,7 +74,6 @@ public class CaptionWindowDecoration extends WindowDecoration<WindowDecorLinearL
    private View.OnTouchListener mOnCaptionTouchListener;
    private DragPositioningCallback mDragPositioningCallback;
    private DragResizeInputListener mDragResizeListener;
    private DragDetector mDragDetector;

    private RelayoutParams mRelayoutParams = new RelayoutParams();
    private final RelayoutResult<WindowDecorLinearLayout> mResult =
@@ -176,12 +175,6 @@ public class CaptionWindowDecoration extends WindowDecoration<WindowDecorLinearL
        return stableBounds.bottom - requiredEmptySpace;
    }


    void setDragDetector(DragDetector dragDetector) {
        mDragDetector = dragDetector;
        mDragDetector.setTouchSlop(ViewConfiguration.get(mContext).getScaledTouchSlop());
    }

    @Override
    void relayout(RunningTaskInfo taskInfo) {
        final SurfaceControl.Transaction t = new SurfaceControl.Transaction();
@@ -288,7 +281,6 @@ public class CaptionWindowDecoration extends WindowDecoration<WindowDecorLinearL

        final int touchSlop = ViewConfiguration.get(mResult.mRootView.getContext())
                .getScaledTouchSlop();
        mDragDetector.setTouchSlop(touchSlop);

        final Resources res = mResult.mRootView.getResources();
        mDragResizeListener.setGeometry(new DragResizeWindowGeometry(0 /* taskCornerRadius */,
+24 −5
Original line number Diff line number Diff line
@@ -79,6 +79,7 @@ import android.view.MotionEvent;
import android.view.SurfaceControl;
import android.view.SurfaceControl.Transaction;
import android.view.View;
import android.view.ViewConfiguration;
import android.widget.Toast;
import android.window.TaskSnapshot;
import android.window.WindowContainerToken;
@@ -651,11 +652,14 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
    private class DesktopModeTouchEventListener extends GestureDetector.SimpleOnGestureListener
            implements View.OnClickListener, View.OnTouchListener, View.OnLongClickListener,
            View.OnGenericMotionListener, DragDetector.MotionEventHandler {
        private static final long APP_HANDLE_HOLD_TO_DRAG_DURATION_MS = 100;
        private static final long APP_HEADER_HOLD_TO_DRAG_DURATION_MS = 0;

        private final int mTaskId;
        private final WindowContainerToken mTaskToken;
        private final DragPositioningCallback mDragPositioningCallback;
        private final DragDetector mDragDetector;
        private final DragDetector mHandleDragDetector;
        private final DragDetector mHeaderDragDetector;
        private final GestureDetector mGestureDetector;
        private final int mDisplayId;
        private final Rect mOnDragStartInitialBounds = new Rect();
@@ -677,7 +681,13 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
            mTaskId = taskInfo.taskId;
            mTaskToken = taskInfo.token;
            mDragPositioningCallback = dragPositioningCallback;
            mDragDetector = new DragDetector(this);
            final int touchSlop = ViewConfiguration.get(mContext).getScaledTouchSlop();
            final long appHandleHoldToDragDuration = Flags.enableHoldToDragAppHandle()
                    ? APP_HANDLE_HOLD_TO_DRAG_DURATION_MS : 0;
            mHandleDragDetector = new DragDetector(this, appHandleHoldToDragDuration,
                    touchSlop);
            mHeaderDragDetector = new DragDetector(this, APP_HEADER_HOLD_TO_DRAG_DURATION_MS,
                    touchSlop);
            mGestureDetector = new GestureDetector(mContext, this);
            mDisplayId = taskInfo.displayId;
        }
@@ -736,7 +746,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
                    && id != R.id.maximize_window && id != R.id.minimize_window) {
                return false;
            }

            final boolean isAppHandle = !getTaskInfo().isFreeform();
            final int actionMasked = e.getActionMasked();
            final boolean isDown = actionMasked == MotionEvent.ACTION_DOWN;
            final boolean isUpOrCancel = actionMasked == MotionEvent.ACTION_CANCEL
@@ -785,7 +795,11 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
                // Gesture is finished, reset state.
                mShouldPilferCaptionEvents = false;
            }
            return mDragDetector.onMotionEvent(v, e);
            if (isAppHandle) {
                return mHandleDragDetector.onMotionEvent(v, e);
            } else {
                return mHeaderDragDetector.onMotionEvent(v, e);
            }
        }

        @Override
@@ -853,6 +867,12 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
            }
        }

        @NonNull
        private RunningTaskInfo getTaskInfo() {
            final DesktopModeWindowDecoration decoration = mWindowDecorByTaskId.get(mTaskId);
            return decoration.mTaskInfo;
        }

        private boolean handleNonFreeformMotionEvent(DesktopModeWindowDecoration decoration,
                View v, MotionEvent e) {
            final int id = v.getId();
@@ -1421,7 +1441,6 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
                touchEventListener, touchEventListener, touchEventListener, touchEventListener);
        windowDecoration.setExclusionRegionListener(mExclusionRegionListener);
        windowDecoration.setDragPositioningCallback(taskPositioner);
        windowDecoration.setDragDetector(touchEventListener.mDragDetector);
        windowDecoration.relayout(taskInfo, startT, finishT,
                false /* applyStartTransactionOnDraw */, false /* shouldSetTaskPositionAndCrop */);
        if (!Flags.enableAdditionalWindowsAboveStatusBar()) {
+0 −7
Original line number Diff line number Diff line
@@ -139,7 +139,6 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
    private Function0<Unit> mOnManageWindowsClickListener;
    private DragPositioningCallback mDragPositioningCallback;
    private DragResizeInputListener mDragResizeListener;
    private DragDetector mDragDetector;
    private RelayoutParams mRelayoutParams = new RelayoutParams();
    private final WindowDecoration.RelayoutResult<WindowDecorLinearLayout> mResult =
            new WindowDecoration.RelayoutResult<>();
@@ -320,11 +319,6 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
        mDragPositioningCallback = dragPositioningCallback;
    }

    void setDragDetector(DragDetector dragDetector) {
        mDragDetector = dragDetector;
        mDragDetector.setTouchSlop(ViewConfiguration.get(mContext).getScaledTouchSlop());
    }

    void setOpenInBrowserClickListener(Consumer<Uri> listener) {
        mOpenInBrowserClickListener = listener;
    }
@@ -482,7 +476,6 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin

        final int touchSlop = ViewConfiguration.get(mResult.mRootView.getContext())
                .getScaledTouchSlop();
        mDragDetector.setTouchSlop(touchSlop);

        // If either task geometry or position have changed, update this task's
        // exclusion region listener
+28 −2
Original line number Diff line number Diff line
@@ -26,6 +26,7 @@ import static android.view.MotionEvent.ACTION_MOVE;
import static android.view.MotionEvent.ACTION_POINTER_UP;
import static android.view.MotionEvent.ACTION_UP;

import android.annotation.NonNull;
import android.graphics.PointF;
import android.view.MotionEvent;
import android.view.View;
@@ -48,12 +49,18 @@ class DragDetector {
    private int mTouchSlop;
    private boolean mIsDragEvent;
    private int mDragPointerId = -1;
    private final long mHoldToDragMinDurationMs;
    private boolean mDidStrayBeforeFullHold;
    private boolean mDidHoldForMinDuration;

    private boolean mResultOfDownAction;

    DragDetector(MotionEventHandler eventHandler) {
    DragDetector(@NonNull MotionEventHandler eventHandler, long holdToDragMinDurationMs,
            int touchSlop) {
        resetState();
        mEventHandler = eventHandler;
        mHoldToDragMinDurationMs = holdToDragMinDurationMs;
        mTouchSlop = touchSlop;
    }

    /**
@@ -101,9 +108,26 @@ class DragDetector {
                if (!mIsDragEvent) {
                    float dx = ev.getRawX(dragPointerIndex) - mInputDownPoint.x;
                    float dy = ev.getRawY(dragPointerIndex) - mInputDownPoint.y;
                    final float dt = ev.getEventTime() - ev.getDownTime();
                    final boolean pastTouchSlop = Math.hypot(dx, dy) > mTouchSlop;
                    final boolean withinHoldRegion = !pastTouchSlop;

                    if (mHoldToDragMinDurationMs <= 0) {
                        mDidHoldForMinDuration = true;
                    } else {
                        if (!withinHoldRegion && dt < mHoldToDragMinDurationMs) {
                            // Mark as having strayed so that in case the (x,y) ends up in the
                            // original position we know it's not actually valid.
                            mDidStrayBeforeFullHold = true;
                        }
                        if (!mDidStrayBeforeFullHold && dt >= mHoldToDragMinDurationMs) {
                            mDidHoldForMinDuration = true;
                        }
                    }

                    // Touches generate noisy moves, so only once the move is past the touch
                    // slop threshold should it be considered a drag.
                    mIsDragEvent = Math.hypot(dx, dy) > mTouchSlop;
                    mIsDragEvent = mDidHoldForMinDuration && pastTouchSlop;
                }
                // The event handler should only be notified about 'move' events if a drag has been
                // detected.
@@ -162,6 +186,8 @@ class DragDetector {
        mInputDownPoint.set(0, 0);
        mDragPointerId = -1;
        mResultOfDownAction = false;
        mDidStrayBeforeFullHold = false;
        mDidHoldForMinDuration = false;
    }

    interface MotionEventHandler {
Loading