diff options
2 files changed, 111 insertions, 10 deletions
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragDetector.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragDetector.java index da268988bac7..3fd3656ccbc5 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragDetector.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragDetector.java @@ -19,7 +19,11 @@ package com.android.wm.shell.windowdecor; import static android.view.InputDevice.SOURCE_TOUCHSCREEN; import static android.view.MotionEvent.ACTION_CANCEL; import static android.view.MotionEvent.ACTION_DOWN; +import static android.view.MotionEvent.ACTION_HOVER_ENTER; +import static android.view.MotionEvent.ACTION_HOVER_EXIT; +import static android.view.MotionEvent.ACTION_HOVER_MOVE; import static android.view.MotionEvent.ACTION_MOVE; +import static android.view.MotionEvent.ACTION_POINTER_UP; import static android.view.MotionEvent.ACTION_UP; import android.graphics.PointF; @@ -43,7 +47,7 @@ class DragDetector { private final PointF mInputDownPoint = new PointF(); private int mTouchSlop; private boolean mIsDragEvent; - private int mDragPointerId; + private int mDragPointerId = -1; private boolean mResultOfDownAction; @@ -67,7 +71,7 @@ class DragDetector { * * @return the result returned by {@link #mEventHandler}, or the result when * {@link #mEventHandler} handles the previous down event if the event shouldn't be passed - */ + */ boolean onMotionEvent(View v, MotionEvent ev) { final boolean isTouchScreen = (ev.getSource() & SOURCE_TOUCHSCREEN) == SOURCE_TOUCHSCREEN; @@ -86,10 +90,14 @@ class DragDetector { return mResultOfDownAction; } case ACTION_MOVE: { - if (ev.findPointerIndex(mDragPointerId) == -1) { - mDragPointerId = ev.getPointerId(0); + if (mDragPointerId == -1) { + // The primary pointer was lifted, ignore the rest of the gesture. + return mResultOfDownAction; } final int dragPointerIndex = ev.findPointerIndex(mDragPointerId); + if (dragPointerIndex == -1) { + throw new IllegalStateException("Failed to find primary pointer!"); + } if (!mIsDragEvent) { float dx = ev.getRawX(dragPointerIndex) - mInputDownPoint.x; float dy = ev.getRawY(dragPointerIndex) - mInputDownPoint.y; @@ -99,22 +107,52 @@ class DragDetector { } // The event handler should only be notified about 'move' events if a drag has been // detected. - if (mIsDragEvent) { - return mEventHandler.handleMotionEvent(v, ev); - } else { + if (!mIsDragEvent) { return mResultOfDownAction; } + return mEventHandler.handleMotionEvent(v, + getSinglePointerEvent(ev, mDragPointerId)); + } + case ACTION_HOVER_ENTER: + case ACTION_HOVER_MOVE: + case ACTION_HOVER_EXIT: { + return mEventHandler.handleMotionEvent(v, + getSinglePointerEvent(ev, mDragPointerId)); + } + case ACTION_POINTER_UP: { + if (mDragPointerId == -1) { + // The primary pointer was lifted, ignore the rest of the gesture. + return mResultOfDownAction; + } + if (mDragPointerId != ev.getPointerId(ev.getActionIndex())) { + // Ignore a secondary pointer being lifted. + return mResultOfDownAction; + } + // The primary pointer is being lifted. + final int dragPointerId = mDragPointerId; + mDragPointerId = -1; + return mEventHandler.handleMotionEvent(v, getSinglePointerEvent(ev, dragPointerId)); } case ACTION_UP: case ACTION_CANCEL: { + final int dragPointerId = mDragPointerId; resetState(); - return mEventHandler.handleMotionEvent(v, ev); + if (dragPointerId == -1) { + // The primary pointer was lifted, ignore the rest of the gesture. + return mResultOfDownAction; + } + return mEventHandler.handleMotionEvent(v, getSinglePointerEvent(ev, dragPointerId)); } default: - return mEventHandler.handleMotionEvent(v, ev); + // Ignore other events. + return mResultOfDownAction; } } + private static MotionEvent getSinglePointerEvent(MotionEvent ev, int pointerId) { + return ev.getPointerCount() > 1 ? ev.split(1 << pointerId) : ev; + } + void setTouchSlop(int touchSlop) { mTouchSlop = touchSlop; } @@ -129,4 +167,4 @@ class DragDetector { interface MotionEventHandler { boolean handleMotionEvent(@Nullable View v, MotionEvent ev); } -} +}
\ No newline at end of file diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DragDetectorTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DragDetectorTest.kt index 3fbab0f9e2bb..56224b4b4025 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DragDetectorTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DragDetectorTest.kt @@ -85,6 +85,23 @@ class DragDetectorTest { } @Test + fun testNoMove_mouse_passesDownAndUp() { + assertTrue(dragDetector.onMotionEvent( + createMotionEvent(MotionEvent.ACTION_DOWN, isTouch = false))) + verify(eventHandler).handleMotionEvent(any(), argThat { + return@argThat it.action == MotionEvent.ACTION_DOWN && it.x == X && it.y == Y && + it.source == InputDevice.SOURCE_MOUSE + }) + + assertTrue(dragDetector.onMotionEvent( + createMotionEvent(MotionEvent.ACTION_UP, isTouch = false))) + verify(eventHandler).handleMotionEvent(any(), argThat { + return@argThat it.action == MotionEvent.ACTION_UP && it.x == X && it.y == Y && + it.source == InputDevice.SOURCE_MOUSE + }) + } + + @Test fun testMoveInSlop_touch_passesDownAndUp() { `when`(eventHandler.handleMotionEvent(any(), argThat { return@argThat it.action == MotionEvent.ACTION_DOWN @@ -166,6 +183,52 @@ class DragDetectorTest { } @Test + fun testDownMoveDown_shouldIgnoreTheSecondDownMotion() { + assertTrue(dragDetector.onMotionEvent(createMotionEvent(MotionEvent.ACTION_DOWN))) + verify(eventHandler).handleMotionEvent(any(), argThat { + return@argThat it.action == MotionEvent.ACTION_DOWN && it.x == X && it.y == Y && + it.source == InputDevice.SOURCE_TOUCHSCREEN + }) + + val newX = X + SLOP + 1 + assertTrue(dragDetector.onMotionEvent(createMotionEvent(MotionEvent.ACTION_MOVE, newX, Y))) + verify(eventHandler).handleMotionEvent(any(), argThat { + return@argThat it.action == MotionEvent.ACTION_MOVE && it.x == newX && it.y == Y && + it.source == InputDevice.SOURCE_TOUCHSCREEN + }) + + assertTrue(dragDetector.onMotionEvent(createMotionEvent(MotionEvent.ACTION_DOWN))) + verify(eventHandler).handleMotionEvent(any(), argThat { + return@argThat it.action == MotionEvent.ACTION_MOVE && it.x == newX && it.y == Y && + it.source == InputDevice.SOURCE_TOUCHSCREEN + }) + } + + @Test + fun testDownMouseMoveDownTouch_shouldIgnoreTheTouchDownMotion() { + assertTrue(dragDetector.onMotionEvent( + createMotionEvent(MotionEvent.ACTION_DOWN, isTouch = false))) + verify(eventHandler).handleMotionEvent(any(), argThat { + return@argThat it.action == MotionEvent.ACTION_DOWN && it.x == X && it.y == Y && + it.source == InputDevice.SOURCE_MOUSE + }) + + val newX = X + SLOP + 1 + assertTrue(dragDetector.onMotionEvent( + createMotionEvent(MotionEvent.ACTION_MOVE, newX, Y, isTouch = false))) + verify(eventHandler).handleMotionEvent(any(), argThat { + return@argThat it.action == MotionEvent.ACTION_MOVE && it.x == newX && it.y == Y && + it.source == InputDevice.SOURCE_MOUSE + }) + + assertTrue(dragDetector.onMotionEvent(createMotionEvent(MotionEvent.ACTION_DOWN))) + verify(eventHandler).handleMotionEvent(any(), argThat { + return@argThat it.action == MotionEvent.ACTION_MOVE && it.x == newX && it.y == Y && + it.source == InputDevice.SOURCE_MOUSE + }) + } + + @Test fun testPassesHoverEnter() { `when`(eventHandler.handleMotionEvent(any(), argThat { it.action == MotionEvent.ACTION_HOVER_ENTER |