diff options
| author | 2023-02-14 18:06:51 -0800 | |
|---|---|---|
| committer | 2023-02-21 17:08:10 +0000 | |
| commit | f372b81eba47c89e26ee320559d59c33e384742c (patch) | |
| tree | 2f57e340346544f049629997a8f18b6b43e015c7 | |
| parent | 16e4fa05e8ae620cb8247de72af8c0038bfee7c3 (diff) | |
Allow new gestures to cancel current gestures
In a previous patch, we chose to ignore new gestures whenever there's
currently an active gesture. Generally this is fine to do, but there are
some concerns around doing that:
1) This is different from the previous behaviour, and some tests were
relying on the previous behaviour.
2) If a test injects an ACTION_DOWN event (globally) and never lifts up
the pointer, this would cause all real subsequent events to be
rejected. That means a bad test can cause device to get into a bad
state.
Rather than adding a special case to deal with 2), let's revert to the
previous behaviour.
Since we are now allowing the new device to take over, and only 1 device
can be active at a time (for now), we must reset the touching pointers
whenever we have a new gesture starting. That's because the function
synthesizeCancelationEventsForAllConnectionsLocked does not modify
TouchState.
We should also be canceling any of the currently hovering pointers by
sending an ACTION_HOVER_EXIT if a touch down occurs. This behaviour
was previously inconsistent in the mouse case.
Once per-device functionality is enabled, this behaviour will be
revisited.
Bug: 268683979
Test: m inputflinger_tests && $ANDROID_HOST_OUT/nativetest64/inputflinger_tests/inputflinger_tests
Merged-In: I10c7ebde7c108baecb67a865f541253fa6e5f7ef
Change-Id: I10c7ebde7c108baecb67a865f541253fa6e5f7ef
(cherry picked from commit 837fab15be20e5960870e69065fda3645ef076ea)
| -rw-r--r-- | services/inputflinger/dispatcher/InputDispatcher.cpp | 42 | ||||
| -rw-r--r-- | services/inputflinger/dispatcher/TouchState.cpp | 16 | ||||
| -rw-r--r-- | services/inputflinger/dispatcher/TouchState.h | 1 | ||||
| -rw-r--r-- | services/inputflinger/tests/InputDispatcher_test.cpp | 431 |
4 files changed, 462 insertions, 28 deletions
diff --git a/services/inputflinger/dispatcher/InputDispatcher.cpp b/services/inputflinger/dispatcher/InputDispatcher.cpp index 3f0d130ecf..08ae376df4 100644 --- a/services/inputflinger/dispatcher/InputDispatcher.cpp +++ b/services/inputflinger/dispatcher/InputDispatcher.cpp @@ -2184,18 +2184,20 @@ std::vector<InputTarget> InputDispatcher::findTouchedWindowTargetsLocked( const bool newGesture = isDown || maskedAction == AMOTION_EVENT_ACTION_SCROLL || isHoverAction; const bool isFromMouse = isFromSource(entry.source, AINPUT_SOURCE_MOUSE); + // If pointers are already down, let's finish the current gesture and ignore the new events + // from another device. However, if the new event is a down event, let's cancel the current + // touch and let the new one take over. + if (switchedDevice && wasDown && !isDown) { + LOG(INFO) << "Dropping event because a pointer for device " << oldState->deviceId + << " is already down in display " << displayId << ": " << entry.getDescription(); + // TODO(b/211379801): test multiple simultaneous input streams. + outInjectionResult = InputEventInjectionResult::FAILED; + return {}; // wrong device + } + if (newGesture) { - // If pointers are already down, let's finish the current gesture and ignore the new events - // from another device. - if (switchedDevice && wasDown) { - ALOGI("Dropping event because a pointer for a different device is already down " - "in display %" PRId32, - displayId); - // TODO(b/211379801): test multiple simultaneous input streams. - outInjectionResult = InputEventInjectionResult::FAILED; - return {}; // wrong device - } - tempTouchState.clearWindowsWithoutPointers(); + // If a new gesture is starting, clear the touch state completely. + tempTouchState.reset(); tempTouchState.deviceId = entry.deviceId; tempTouchState.source = entry.source; isSplit = false; @@ -2317,6 +2319,10 @@ std::vector<InputTarget> InputDispatcher::findTouchedWindowTargetsLocked( const bool isDownOrPointerDown = maskedAction == AMOTION_EVENT_ACTION_DOWN || maskedAction == AMOTION_EVENT_ACTION_POINTER_DOWN; + // TODO(b/211379801): Currently, even if pointerIds are empty (hover case), we would + // still add a window to the touch state. We should avoid doing that, but some of the + // later checks ("at least one foreground window") rely on this in order to dispatch + // the event properly, so that needs to be updated, possibly by looking at InputTargets. tempTouchState.addOrUpdateWindow(windowHandle, targetFlags, pointerIds, isDownOrPointerDown ? std::make_optional(entry.eventTime) @@ -2369,10 +2375,9 @@ std::vector<InputTarget> InputDispatcher::findTouchedWindowTargetsLocked( // If the pointer is not currently down, then ignore the event. if (!tempTouchState.isDown()) { - ALOGD_IF(DEBUG_FOCUS, - "Dropping event because the pointer is not down or we previously " - "dropped the pointer down event in display %" PRId32 ": %s", - displayId, entry.getDescription().c_str()); + LOG(INFO) << "Dropping event because the pointer is not down or we previously " + "dropped the pointer down event in display " + << displayId << ": " << entry.getDescription(); outInjectionResult = InputEventInjectionResult::FAILED; return {}; } @@ -2530,7 +2535,6 @@ std::vector<InputTarget> InputDispatcher::findTouchedWindowTargetsLocked( } // Success! Output targets from the touch state. - tempTouchState.clearWindowsWithoutPointers(); for (const TouchedWindow& touchedWindow : tempTouchState.windows) { if (touchedWindow.pointerIds.none() && !touchedWindow.hasHoveringPointers(entry.deviceId)) { // Windows with hovering pointers are getting persisted inside TouchState. @@ -2570,14 +2574,13 @@ std::vector<InputTarget> InputDispatcher::findTouchedWindowTargetsLocked( } else if (maskedAction == AMOTION_EVENT_ACTION_UP) { // Pointer went up. tempTouchState.removeTouchedPointer(entry.pointerProperties[0].id); - tempTouchState.clearWindowsWithoutPointers(); } else if (maskedAction == AMOTION_EVENT_ACTION_CANCEL) { // All pointers up or canceled. tempTouchState.reset(); } else if (maskedAction == AMOTION_EVENT_ACTION_DOWN) { // First pointer went down. - if (oldState && oldState->isDown()) { - ALOGD("Conflicting pointer actions: Down received while already down."); + if (oldState && (oldState->isDown() || oldState->hasHoveringPointers())) { + ALOGD("Conflicting pointer actions: Down received while already down or hovering."); *outConflictingPointerActions = true; } } else if (maskedAction == AMOTION_EVENT_ACTION_POINTER_UP) { @@ -2600,6 +2603,7 @@ std::vector<InputTarget> InputDispatcher::findTouchedWindowTargetsLocked( // state was only valid for this one action. if (maskedAction != AMOTION_EVENT_ACTION_SCROLL) { if (displayId >= 0) { + tempTouchState.clearWindowsWithoutPointers(); mTouchStatesByDisplay[displayId] = tempTouchState; } else { mTouchStatesByDisplay.erase(displayId); diff --git a/services/inputflinger/dispatcher/TouchState.cpp b/services/inputflinger/dispatcher/TouchState.cpp index 425847183e..9c443f14cf 100644 --- a/services/inputflinger/dispatcher/TouchState.cpp +++ b/services/inputflinger/dispatcher/TouchState.cpp @@ -35,6 +35,7 @@ void TouchState::removeTouchedPointer(int32_t pointerId) { for (TouchedWindow& touchedWindow : windows) { touchedWindow.removeTouchingPointer(pointerId); } + clearWindowsWithoutPointers(); } void TouchState::removeTouchedPointerFromWindow( @@ -42,6 +43,7 @@ void TouchState::removeTouchedPointerFromWindow( for (TouchedWindow& touchedWindow : windows) { if (touchedWindow.windowHandle == windowHandle) { touchedWindow.removeTouchingPointer(pointerId); + clearWindowsWithoutPointers(); return; } } @@ -51,6 +53,7 @@ void TouchState::clearHoveringPointers() { for (TouchedWindow& touchedWindow : windows) { touchedWindow.clearHoveringPointers(); } + clearWindowsWithoutPointers(); } void TouchState::clearWindowsWithoutPointers() { @@ -135,7 +138,7 @@ void TouchState::cancelPointersForWindowsExcept(std::bitset<MAX_POINTER_ID + 1> w.pointerIds &= ~pointerIds; } }); - std::erase_if(windows, [](const TouchedWindow& w) { return w.pointerIds.none(); }); + clearWindowsWithoutPointers(); } /** @@ -164,7 +167,7 @@ void TouchState::cancelPointersForNonPilferingWindows() { w.pilferedPointerIds ^ allPilferedPointerIds; w.pointerIds &= ~pilferedByOtherWindows; }); - std::erase_if(windows, [](const TouchedWindow& w) { return w.pointerIds.none(); }); + clearWindowsWithoutPointers(); } sp<WindowInfoHandle> TouchState::getFirstForegroundWindowHandle() const { @@ -216,6 +219,11 @@ bool TouchState::isDown() const { [](const TouchedWindow& window) { return window.pointerIds.any(); }); } +bool TouchState::hasHoveringPointers() const { + return std::any_of(windows.begin(), windows.end(), + [](const TouchedWindow& window) { return window.hasHoveringPointers(); }); +} + std::set<sp<WindowInfoHandle>> TouchState::getWindowsWithHoveringPointer(int32_t hoveringDeviceId, int32_t pointerId) const { std::set<sp<WindowInfoHandle>> out; @@ -231,9 +239,7 @@ void TouchState::removeHoveringPointer(int32_t hoveringDeviceId, int32_t hoverin for (TouchedWindow& window : windows) { window.removeHoveringPointer(hoveringDeviceId, hoveringPointerId); } - std::erase_if(windows, [](const TouchedWindow& w) { - return w.pointerIds.none() && !w.hasHoveringPointers(); - }); + clearWindowsWithoutPointers(); } std::string TouchState::dump() const { diff --git a/services/inputflinger/dispatcher/TouchState.h b/services/inputflinger/dispatcher/TouchState.h index 6e965d8c96..a20080f534 100644 --- a/services/inputflinger/dispatcher/TouchState.h +++ b/services/inputflinger/dispatcher/TouchState.h @@ -71,6 +71,7 @@ struct TouchState { const sp<android::gui::WindowInfoHandle>& windowHandle) const; // Whether any of the windows are currently being touched bool isDown() const; + bool hasHoveringPointers() const; std::set<sp<android::gui::WindowInfoHandle>> getWindowsWithHoveringPointer( int32_t deviceId, int32_t pointerId) const; diff --git a/services/inputflinger/tests/InputDispatcher_test.cpp b/services/inputflinger/tests/InputDispatcher_test.cpp index e71cdce498..bd9038d42e 100644 --- a/services/inputflinger/tests/InputDispatcher_test.cpp +++ b/services/inputflinger/tests/InputDispatcher_test.cpp @@ -58,8 +58,23 @@ static constexpr int32_t SECOND_DEVICE_ID = 2; static constexpr int32_t DISPLAY_ID = ADISPLAY_ID_DEFAULT; static constexpr int32_t SECOND_DISPLAY_ID = 1; +static constexpr int32_t ACTION_DOWN = AMOTION_EVENT_ACTION_DOWN; +static constexpr int32_t ACTION_MOVE = AMOTION_EVENT_ACTION_MOVE; +static constexpr int32_t ACTION_UP = AMOTION_EVENT_ACTION_UP; +static constexpr int32_t ACTION_HOVER_ENTER = AMOTION_EVENT_ACTION_HOVER_ENTER; +static constexpr int32_t ACTION_HOVER_EXIT = AMOTION_EVENT_ACTION_HOVER_EXIT; static constexpr int32_t ACTION_OUTSIDE = AMOTION_EVENT_ACTION_OUTSIDE; static constexpr int32_t ACTION_CANCEL = AMOTION_EVENT_ACTION_CANCEL; +/** + * The POINTER_DOWN(0) is an unusual, but valid, action. It just means that the new pointer in the + * MotionEvent is at the index 0 rather than 1 (or later). That is, the pointer id=0 (which is at + * index 0) is the new pointer going down. The same pointer could have been placed at a different + * index, and the action would become POINTER_1_DOWN, 2, etc..; these would all be valid. In + * general, we try to place pointer id = 0 at the index 0. Of course, this is not possible if + * pointer id=0 leaves but the pointer id=1 remains. + */ +static constexpr int32_t POINTER_0_DOWN = + AMOTION_EVENT_ACTION_POINTER_DOWN | (0 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT); static constexpr int32_t POINTER_1_DOWN = AMOTION_EVENT_ACTION_POINTER_DOWN | (1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT); static constexpr int32_t POINTER_2_DOWN = @@ -145,6 +160,10 @@ MATCHER_P(WithDisplayId, displayId, "InputEvent with specified displayId") { return arg.getDisplayId() == displayId; } +MATCHER_P(WithDeviceId, deviceId, "InputEvent with specified deviceId") { + return arg.getDeviceId() == deviceId; +} + MATCHER_P(WithSource, source, "InputEvent with specified source") { *result_listener << "expected source " << inputEventSourceToString(source) << ", but got " << inputEventSourceToString(arg.getSource()); @@ -163,6 +182,10 @@ MATCHER_P2(WithCoords, x, y, "MotionEvent with specified coordinates") { return arg.getX(/*pointerIndex=*/0) == x && arg.getY(/*pointerIndex=*/0) == y; } +MATCHER_P(WithPointerCount, pointerCount, "MotionEvent with specified number of pointers") { + return arg.getPointerCount() == pointerCount; +} + MATCHER_P(WithPointers, pointers, "MotionEvent with specified pointers") { // Build a map for the received pointers, by pointer id std::map<int32_t /*pointerId*/, PointF> actualPointers; @@ -2365,6 +2388,165 @@ TEST_F(InputDispatcherTest, HoverFromLeftToRightAndTap) { } /** + * Two windows: a window on the left and a window on the right. + * Mouse is clicked on the left window and remains down. Touch is touched on the right and remains + * down. Then, on the left window, also place second touch pointer down. + * This test tries to reproduce a crash. + * In the buggy implementation, second pointer down on the left window would cause a crash. + */ +TEST_F(InputDispatcherTest, MultiDeviceSplitTouch) { + std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>(); + sp<FakeWindowHandle> leftWindow = + sp<FakeWindowHandle>::make(application, mDispatcher, "Left", ADISPLAY_ID_DEFAULT); + leftWindow->setFrame(Rect(0, 0, 200, 200)); + + sp<FakeWindowHandle> rightWindow = + sp<FakeWindowHandle>::make(application, mDispatcher, "Right", ADISPLAY_ID_DEFAULT); + rightWindow->setFrame(Rect(200, 0, 400, 200)); + + mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {leftWindow, rightWindow}}}); + + const int32_t touchDeviceId = 4; + const int32_t mouseDeviceId = 6; + NotifyMotionArgs args; + + // Start hovering over the left window + mDispatcher->notifyMotion(&( + args = MotionArgsBuilder(ACTION_HOVER_ENTER, AINPUT_SOURCE_MOUSE) + .deviceId(mouseDeviceId) + .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_MOUSE).x(100).y(100)) + .build())); + leftWindow->consumeMotionEvent( + AllOf(WithMotionAction(ACTION_HOVER_ENTER), WithDeviceId(mouseDeviceId))); + + // Mouse down on left window + mDispatcher->notifyMotion(&( + args = MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_MOUSE) + .deviceId(mouseDeviceId) + .buttonState(AMOTION_EVENT_BUTTON_PRIMARY) + .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_MOUSE).x(100).y(100)) + .build())); + + leftWindow->consumeMotionEvent( + AllOf(WithMotionAction(ACTION_HOVER_EXIT), WithDeviceId(mouseDeviceId))); + leftWindow->consumeMotionEvent( + AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(mouseDeviceId))); + + mDispatcher->notifyMotion(&( + args = MotionArgsBuilder(AMOTION_EVENT_ACTION_BUTTON_PRESS, AINPUT_SOURCE_MOUSE) + .deviceId(mouseDeviceId) + .buttonState(AMOTION_EVENT_BUTTON_PRIMARY) + .actionButton(AMOTION_EVENT_BUTTON_PRIMARY) + .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_MOUSE).x(100).y(100)) + .build())); + leftWindow->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_PRESS)); + + // First touch pointer down on right window + mDispatcher->notifyMotion(&( + args = MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN) + .deviceId(touchDeviceId) + .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_FINGER).x(300).y(100)) + .build())); + leftWindow->consumeMotionEvent(WithMotionAction(ACTION_CANCEL)); + + rightWindow->consumeMotionEvent(WithMotionAction(ACTION_DOWN)); + + // Second touch pointer down on left window + mDispatcher->notifyMotion(&( + args = MotionArgsBuilder(POINTER_1_DOWN, AINPUT_SOURCE_TOUCHSCREEN) + .deviceId(touchDeviceId) + .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_FINGER).x(300).y(100)) + .pointer(PointerBuilder(1, AMOTION_EVENT_TOOL_TYPE_FINGER).x(100).y(100)) + .build())); + leftWindow->consumeMotionEvent( + AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(touchDeviceId))); + // This MOVE event is not necessary (doesn't carry any new information), but it's there in the + // current implementation. + const std::map<int32_t, PointF> expectedPointers{{0, PointF{100, 100}}}; + rightWindow->consumeMotionEvent( + AllOf(WithMotionAction(ACTION_MOVE), WithPointers(expectedPointers))); + + leftWindow->assertNoEvents(); + rightWindow->assertNoEvents(); +} + +/** + * On a single window, use two different devices: mouse and touch. + * Touch happens first, with two pointers going down, and then the first pointer leaving. + * Mouse is clicked next, which causes the touch stream to be aborted with ACTION_CANCEL. + * Finally, a second touch pointer goes down again. Ensure the second touch pointer is ignored, + * because the mouse is currently down, and a POINTER_DOWN event from the touchscreen does not + * represent a new gesture. + */ +TEST_F(InputDispatcherTest, MixedTouchAndMouseWithPointerDown) { + std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>(); + sp<FakeWindowHandle> window = + sp<FakeWindowHandle>::make(application, mDispatcher, "Window", ADISPLAY_ID_DEFAULT); + window->setFrame(Rect(0, 0, 400, 400)); + + mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {window}}}); + + const int32_t touchDeviceId = 4; + const int32_t mouseDeviceId = 6; + NotifyMotionArgs args; + + // First touch pointer down on right window + mDispatcher->notifyMotion(&( + args = MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN) + .deviceId(touchDeviceId) + .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_FINGER).x(300).y(100)) + .build())); + // Second touch pointer down + mDispatcher->notifyMotion(&( + args = MotionArgsBuilder(POINTER_1_DOWN, AINPUT_SOURCE_TOUCHSCREEN) + .deviceId(touchDeviceId) + .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_FINGER).x(300).y(100)) + .pointer(PointerBuilder(1, AMOTION_EVENT_TOOL_TYPE_FINGER).x(350).y(100)) + .build())); + // First touch pointer lifts. The second one remains down + mDispatcher->notifyMotion(&( + args = MotionArgsBuilder(POINTER_0_UP, AINPUT_SOURCE_TOUCHSCREEN) + .deviceId(touchDeviceId) + .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_FINGER).x(300).y(100)) + .pointer(PointerBuilder(1, AMOTION_EVENT_TOOL_TYPE_FINGER).x(350).y(100)) + .build())); + window->consumeMotionEvent(WithMotionAction(ACTION_DOWN)); + window->consumeMotionEvent(WithMotionAction(POINTER_1_DOWN)); + window->consumeMotionEvent(WithMotionAction(POINTER_0_UP)); + + // Mouse down. The touch should be canceled + mDispatcher->notifyMotion(&( + args = MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_MOUSE) + .deviceId(mouseDeviceId) + .buttonState(AMOTION_EVENT_BUTTON_PRIMARY) + .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_MOUSE).x(320).y(100)) + .build())); + + window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_CANCEL), WithDeviceId(touchDeviceId), + WithPointerCount(2u))); + window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(mouseDeviceId))); + + mDispatcher->notifyMotion(&( + args = MotionArgsBuilder(AMOTION_EVENT_ACTION_BUTTON_PRESS, AINPUT_SOURCE_MOUSE) + .deviceId(mouseDeviceId) + .buttonState(AMOTION_EVENT_BUTTON_PRIMARY) + .actionButton(AMOTION_EVENT_BUTTON_PRIMARY) + .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_MOUSE).x(320).y(100)) + .build())); + window->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_PRESS)); + + // Second touch pointer down. + mDispatcher->notifyMotion(&( + args = MotionArgsBuilder(POINTER_0_DOWN, AINPUT_SOURCE_TOUCHSCREEN) + .deviceId(touchDeviceId) + .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_FINGER).x(300).y(100)) + .pointer(PointerBuilder(1, AMOTION_EVENT_TOOL_TYPE_FINGER).x(350).y(100)) + .build())); + // The pointer_down event should be ignored + window->assertNoEvents(); +} + +/** * This test is similar to the test above, but the sequence of injected events is different. * * Two windows: a window on the left and a window on the right. @@ -2541,6 +2723,182 @@ TEST_F(InputDispatcherTest, StylusHoverAndTouchTap) { } /** + * A spy window above a window with no input channel. + * Start hovering with a stylus device, and then tap with it. + * Ensure spy window receives the entire sequence. + */ +TEST_F(InputDispatcherTest, StylusHoverAndDownNoInputChannel) { + std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>(); + sp<FakeWindowHandle> spyWindow = + sp<FakeWindowHandle>::make(application, mDispatcher, "Spy", ADISPLAY_ID_DEFAULT); + spyWindow->setFrame(Rect(0, 0, 200, 200)); + spyWindow->setTrustedOverlay(true); + spyWindow->setSpy(true); + sp<FakeWindowHandle> window = + sp<FakeWindowHandle>::make(application, mDispatcher, "Window", ADISPLAY_ID_DEFAULT); + window->setNoInputChannel(true); + window->setFrame(Rect(0, 0, 200, 200)); + + mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {spyWindow, window}}}); + + NotifyMotionArgs args; + + // Start hovering with stylus + mDispatcher->notifyMotion( + &(args = MotionArgsBuilder(ACTION_HOVER_ENTER, AINPUT_SOURCE_STYLUS) + .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_STYLUS).x(50).y(50)) + .build())); + spyWindow->consumeMotionEvent(WithMotionAction(ACTION_HOVER_ENTER)); + // Stop hovering + mDispatcher->notifyMotion( + &(args = MotionArgsBuilder(ACTION_HOVER_EXIT, AINPUT_SOURCE_STYLUS) + .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_STYLUS).x(50).y(50)) + .build())); + spyWindow->consumeMotionEvent(WithMotionAction(ACTION_HOVER_EXIT)); + + // Stylus touches down + mDispatcher->notifyMotion( + &(args = MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_STYLUS) + .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_STYLUS).x(50).y(50)) + .build())); + spyWindow->consumeMotionEvent(WithMotionAction(ACTION_DOWN)); + + // Stylus goes up + mDispatcher->notifyMotion( + &(args = MotionArgsBuilder(ACTION_UP, AINPUT_SOURCE_STYLUS) + .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_STYLUS).x(50).y(50)) + .build())); + spyWindow->consumeMotionEvent(WithMotionAction(ACTION_UP)); + + // Again hover + mDispatcher->notifyMotion( + &(args = MotionArgsBuilder(ACTION_HOVER_ENTER, AINPUT_SOURCE_STYLUS) + .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_STYLUS).x(50).y(50)) + .build())); + spyWindow->consumeMotionEvent(WithMotionAction(ACTION_HOVER_ENTER)); + // Stop hovering + mDispatcher->notifyMotion( + &(args = MotionArgsBuilder(ACTION_HOVER_EXIT, AINPUT_SOURCE_STYLUS) + .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_STYLUS).x(50).y(50)) + .build())); + spyWindow->consumeMotionEvent(WithMotionAction(ACTION_HOVER_EXIT)); + + // No more events + spyWindow->assertNoEvents(); + window->assertNoEvents(); +} + +/** + * Start hovering with a mouse, and then tap with a touch device. Pilfer the touch stream. + * Next, click with the mouse device. Both windows (spy and regular) should receive the new mouse + * ACTION_DOWN event because that's a new gesture, and pilfering should no longer be active. + * While the mouse is down, new move events from the touch device should be ignored. + */ +TEST_F(InputDispatcherTest, TouchPilferAndMouseMove) { + std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>(); + sp<FakeWindowHandle> spyWindow = + sp<FakeWindowHandle>::make(application, mDispatcher, "Spy", ADISPLAY_ID_DEFAULT); + spyWindow->setFrame(Rect(0, 0, 200, 200)); + spyWindow->setTrustedOverlay(true); + spyWindow->setSpy(true); + sp<FakeWindowHandle> window = + sp<FakeWindowHandle>::make(application, mDispatcher, "Window", ADISPLAY_ID_DEFAULT); + window->setFrame(Rect(0, 0, 200, 200)); + + mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {spyWindow, window}}}); + + const int32_t mouseDeviceId = 7; + const int32_t touchDeviceId = 4; + NotifyMotionArgs args; + + // Hover a bit with mouse first + mDispatcher->notifyMotion(&( + args = MotionArgsBuilder(ACTION_HOVER_ENTER, AINPUT_SOURCE_MOUSE) + .deviceId(mouseDeviceId) + .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_MOUSE).x(100).y(100)) + .build())); + spyWindow->consumeMotionEvent( + AllOf(WithMotionAction(ACTION_HOVER_ENTER), WithDeviceId(mouseDeviceId))); + window->consumeMotionEvent( + AllOf(WithMotionAction(ACTION_HOVER_ENTER), WithDeviceId(mouseDeviceId))); + + // Start touching + mDispatcher->notifyMotion( + &(args = MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN) + .deviceId(touchDeviceId) + .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_FINGER).x(50).y(50)) + .build())); + spyWindow->consumeMotionEvent(WithMotionAction(ACTION_HOVER_EXIT)); + window->consumeMotionEvent(WithMotionAction(ACTION_HOVER_EXIT)); + spyWindow->consumeMotionEvent(WithMotionAction(ACTION_DOWN)); + window->consumeMotionEvent(WithMotionAction(ACTION_DOWN)); + + mDispatcher->notifyMotion( + &(args = MotionArgsBuilder(ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN) + .deviceId(touchDeviceId) + .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_FINGER).x(55).y(55)) + .build())); + spyWindow->consumeMotionEvent(WithMotionAction(ACTION_MOVE)); + window->consumeMotionEvent(WithMotionAction(ACTION_MOVE)); + + // Pilfer the stream + EXPECT_EQ(OK, mDispatcher->pilferPointers(spyWindow->getToken())); + window->consumeMotionEvent(WithMotionAction(ACTION_CANCEL)); + + mDispatcher->notifyMotion( + &(args = MotionArgsBuilder(ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN) + .deviceId(touchDeviceId) + .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_FINGER).x(60).y(60)) + .build())); + spyWindow->consumeMotionEvent(WithMotionAction(ACTION_MOVE)); + + // Mouse down + mDispatcher->notifyMotion(&( + args = MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_MOUSE) + .deviceId(mouseDeviceId) + .buttonState(AMOTION_EVENT_BUTTON_PRIMARY) + .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_MOUSE).x(100).y(100)) + .build())); + + spyWindow->consumeMotionEvent( + AllOf(WithMotionAction(ACTION_CANCEL), WithDeviceId(touchDeviceId))); + spyWindow->consumeMotionEvent( + AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(mouseDeviceId))); + window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(mouseDeviceId))); + + mDispatcher->notifyMotion(&( + args = MotionArgsBuilder(AMOTION_EVENT_ACTION_BUTTON_PRESS, AINPUT_SOURCE_MOUSE) + .deviceId(mouseDeviceId) + .buttonState(AMOTION_EVENT_BUTTON_PRIMARY) + .actionButton(AMOTION_EVENT_BUTTON_PRIMARY) + .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_MOUSE).x(100).y(100)) + .build())); + spyWindow->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_PRESS)); + window->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_PRESS)); + + // Mouse move! + mDispatcher->notifyMotion(&( + args = MotionArgsBuilder(ACTION_MOVE, AINPUT_SOURCE_MOUSE) + .deviceId(mouseDeviceId) + .buttonState(AMOTION_EVENT_BUTTON_PRIMARY) + .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_MOUSE).x(110).y(110)) + .build())); + spyWindow->consumeMotionEvent(WithMotionAction(ACTION_MOVE)); + window->consumeMotionEvent(WithMotionAction(ACTION_MOVE)); + + // Touch move! + mDispatcher->notifyMotion( + &(args = MotionArgsBuilder(ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN) + .deviceId(touchDeviceId) + .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_FINGER).x(65).y(65)) + .build())); + + // No more events + spyWindow->assertNoEvents(); + window->assertNoEvents(); +} + +/** * On the display, have a single window, and also an area where there's no window. * First pointer touches the "no window" area of the screen. Second pointer touches the window. * Make sure that the window receives the second pointer, and first pointer is simply ignored. @@ -2705,7 +3063,8 @@ TEST_F(InputDispatcherTest, HoverMoveEnterMouseClickAndHoverMoveExit) { .x(300) .y(400)) .build())); - windowLeft->consumeMotionDown(ADISPLAY_ID_DEFAULT); + windowLeft->consumeMotionEvent(WithMotionAction(ACTION_HOVER_EXIT)); + windowLeft->consumeMotionEvent(WithMotionAction(ACTION_DOWN)); ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, injectMotionEvent(mDispatcher, @@ -2750,7 +3109,6 @@ TEST_F(InputDispatcherTest, HoverMoveEnterMouseClickAndHoverMoveExit) { .x(900) .y(400)) .build())); - windowLeft->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_EXIT)); windowRight->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER)); // No more events @@ -2758,6 +3116,70 @@ TEST_F(InputDispatcherTest, HoverMoveEnterMouseClickAndHoverMoveExit) { windowRight->assertNoEvents(); } +/** + * Put two fingers down (and don't release them) and click the mouse button. + * The clicking of mouse is a new ACTION_DOWN event. Since it's from a different device, the + * currently active gesture should be canceled, and the new one should proceed. + */ +TEST_F(InputDispatcherTest, TwoPointersDownMouseClick) { + std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>(); + sp<FakeWindowHandle> window = + sp<FakeWindowHandle>::make(application, mDispatcher, "Window", ADISPLAY_ID_DEFAULT); + window->setFrame(Rect(0, 0, 600, 800)); + + mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {window}}}); + + const int32_t touchDeviceId = 4; + const int32_t mouseDeviceId = 6; + NotifyMotionArgs args; + + // Two pointers down + mDispatcher->notifyMotion(&( + args = MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN) + .deviceId(touchDeviceId) + .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_FINGER).x(100).y(100)) + .build())); + + mDispatcher->notifyMotion(&( + args = MotionArgsBuilder(POINTER_1_DOWN, AINPUT_SOURCE_TOUCHSCREEN) + .deviceId(touchDeviceId) + .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_FINGER).x(100).y(100)) + .pointer(PointerBuilder(1, AMOTION_EVENT_TOOL_TYPE_FINGER).x(120).y(120)) + .build())); + window->consumeMotionEvent(WithMotionAction(ACTION_DOWN)); + window->consumeMotionEvent(WithMotionAction(POINTER_1_DOWN)); + + // Inject a series of mouse events for a mouse click + mDispatcher->notifyMotion(&( + args = MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_MOUSE) + .deviceId(mouseDeviceId) + .buttonState(AMOTION_EVENT_BUTTON_PRIMARY) + .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_MOUSE).x(300).y(400)) + .build())); + window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_CANCEL), WithDeviceId(touchDeviceId), + WithPointerCount(2u))); + window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(mouseDeviceId))); + + mDispatcher->notifyMotion(&( + args = MotionArgsBuilder(AMOTION_EVENT_ACTION_BUTTON_PRESS, AINPUT_SOURCE_MOUSE) + .deviceId(mouseDeviceId) + .buttonState(AMOTION_EVENT_BUTTON_PRIMARY) + .actionButton(AMOTION_EVENT_BUTTON_PRIMARY) + .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_MOUSE).x(300).y(400)) + .build())); + window->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_PRESS)); + + // Try to send more touch events while the mouse is down. Since it's a continuation of an + // already canceled gesture, it should be ignored. + mDispatcher->notifyMotion(&( + args = MotionArgsBuilder(ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN) + .deviceId(touchDeviceId) + .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_FINGER).x(101).y(101)) + .pointer(PointerBuilder(1, AMOTION_EVENT_TOOL_TYPE_FINGER).x(121).y(121)) + .build())); + window->assertNoEvents(); +} + TEST_F(InputDispatcherTest, HoverWithSpyWindows) { std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>(); @@ -2940,7 +3362,8 @@ TEST_F(InputDispatcherTest, HoverEnterMouseClickAndHoverExit) { .x(300) .y(400)) .build())); - window->consumeMotionDown(ADISPLAY_ID_DEFAULT); + window->consumeMotionEvent(WithMotionAction(ACTION_HOVER_EXIT)); + window->consumeMotionEvent(WithMotionAction(ACTION_DOWN)); ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, injectMotionEvent(mDispatcher, @@ -2984,7 +3407,7 @@ TEST_F(InputDispatcherTest, HoverEnterMouseClickAndHoverExit) { .x(300) .y(400)) .build())); - window->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_EXIT)); + window->assertNoEvents(); } /** |