diff options
-rw-r--r-- | services/inputflinger/dispatcher/CancelationOptions.h | 3 | ||||
-rw-r--r-- | services/inputflinger/dispatcher/InputDispatcher.cpp | 33 | ||||
-rw-r--r-- | services/inputflinger/dispatcher/InputState.cpp | 2 | ||||
-rw-r--r-- | services/inputflinger/dispatcher/TouchState.cpp | 7 | ||||
-rw-r--r-- | services/inputflinger/dispatcher/TouchState.h | 3 | ||||
-rw-r--r-- | services/inputflinger/dispatcher/TouchedWindow.cpp | 50 | ||||
-rw-r--r-- | services/inputflinger/dispatcher/TouchedWindow.h | 13 | ||||
-rw-r--r-- | services/inputflinger/tests/InputDispatcher_test.cpp | 93 |
8 files changed, 185 insertions, 19 deletions
diff --git a/services/inputflinger/dispatcher/CancelationOptions.h b/services/inputflinger/dispatcher/CancelationOptions.h index 4a0889f596..568d348ba9 100644 --- a/services/inputflinger/dispatcher/CancelationOptions.h +++ b/services/inputflinger/dispatcher/CancelationOptions.h @@ -32,7 +32,8 @@ struct CancelationOptions { CANCEL_POINTER_EVENTS = 1, CANCEL_NON_POINTER_EVENTS = 2, CANCEL_FALLBACK_EVENTS = 3, - ftl_last = CANCEL_FALLBACK_EVENTS, + CANCEL_HOVER_EVENTS = 4, + ftl_last = CANCEL_HOVER_EVENTS }; // The criterion to use to determine which events should be canceled. diff --git a/services/inputflinger/dispatcher/InputDispatcher.cpp b/services/inputflinger/dispatcher/InputDispatcher.cpp index 2161e0985b..7eb7e36386 100644 --- a/services/inputflinger/dispatcher/InputDispatcher.cpp +++ b/services/inputflinger/dispatcher/InputDispatcher.cpp @@ -746,7 +746,8 @@ std::vector<TouchedWindow> getHoveringWindowsLocked(const TouchState* oldState, } touchedWindow.dispatchMode = InputTarget::DispatchMode::AS_IS; } - touchedWindow.addHoveringPointer(entry.deviceId, pointer); + const auto [x, y] = resolveTouchedPosition(entry); + touchedWindow.addHoveringPointer(entry.deviceId, pointer, x, y); if (canReceiveForegroundTouches(*newWindow->getInfo())) { touchedWindow.targetFlags |= InputTarget::Flags::FOREGROUND; } @@ -873,6 +874,8 @@ std::pair<bool /*cancelPointers*/, bool /*cancelNonPointers*/> expandCancellatio return {false, true}; case CancelationOptions::Mode::CANCEL_FALLBACK_EVENTS: return {false, true}; + case CancelationOptions::Mode::CANCEL_HOVER_EVENTS: + return {true, false}; } } @@ -2511,7 +2514,8 @@ std::vector<InputTarget> InputDispatcher::findTouchedWindowTargetsLocked( if (isHoverAction) { // The "windowHandle" is the target of this hovering pointer. - tempTouchState.addHoveringPointerToWindow(windowHandle, entry.deviceId, pointer); + tempTouchState.addHoveringPointerToWindow(windowHandle, entry.deviceId, pointer, x, + y); } // Set target flags. @@ -5437,6 +5441,31 @@ void InputDispatcher::setInputWindowsLocked( } } + // Check if the hovering should stop because the window is no longer eligible to receive it + // (for example, if the touchable region changed) + if (const auto& it = mTouchStatesByDisplay.find(displayId); it != mTouchStatesByDisplay.end()) { + TouchState& state = it->second; + for (TouchedWindow& touchedWindow : state.windows) { + std::vector<DeviceId> erasedDevices = touchedWindow.eraseHoveringPointersIf( + [this, displayId, &touchedWindow](const PointerProperties& properties, float x, + float y) REQUIRES(mLock) { + const bool isStylus = properties.toolType == ToolType::STYLUS; + const ui::Transform displayTransform = getTransformLocked(displayId); + const bool stillAcceptsTouch = + windowAcceptsTouchAt(*touchedWindow.windowHandle->getInfo(), + displayId, x, y, isStylus, displayTransform); + return !stillAcceptsTouch; + }); + + for (DeviceId deviceId : erasedDevices) { + CancelationOptions options(CancelationOptions::Mode::CANCEL_HOVER_EVENTS, + "WindowInfo changed", traceContext.getTracker()); + options.deviceId = deviceId; + synthesizeCancelationEventsForWindowLocked(touchedWindow.windowHandle, options); + } + } + } + // Release information for windows that are no longer present. // This ensures that unused input channels are released promptly. // Otherwise, they might stick around until the window handle is destroyed diff --git a/services/inputflinger/dispatcher/InputState.cpp b/services/inputflinger/dispatcher/InputState.cpp index dfbe02f332..e283fc3b4d 100644 --- a/services/inputflinger/dispatcher/InputState.cpp +++ b/services/inputflinger/dispatcher/InputState.cpp @@ -638,6 +638,8 @@ bool InputState::shouldCancelMotion(const MotionMemento& memento, return memento.source & AINPUT_SOURCE_CLASS_POINTER; case CancelationOptions::Mode::CANCEL_NON_POINTER_EVENTS: return !(memento.source & AINPUT_SOURCE_CLASS_POINTER); + case CancelationOptions::Mode::CANCEL_HOVER_EVENTS: + return memento.hovering; default: return false; } diff --git a/services/inputflinger/dispatcher/TouchState.cpp b/services/inputflinger/dispatcher/TouchState.cpp index 0c9ad3c7b7..2bf63beb05 100644 --- a/services/inputflinger/dispatcher/TouchState.cpp +++ b/services/inputflinger/dispatcher/TouchState.cpp @@ -112,17 +112,18 @@ android::base::Result<void> TouchState::addOrUpdateWindow( } void TouchState::addHoveringPointerToWindow(const sp<WindowInfoHandle>& windowHandle, - DeviceId deviceId, const PointerProperties& pointer) { + DeviceId deviceId, const PointerProperties& pointer, + float x, float y) { for (TouchedWindow& touchedWindow : windows) { if (touchedWindow.windowHandle == windowHandle) { - touchedWindow.addHoveringPointer(deviceId, pointer); + touchedWindow.addHoveringPointer(deviceId, pointer, x, y); return; } } TouchedWindow touchedWindow; touchedWindow.windowHandle = windowHandle; - touchedWindow.addHoveringPointer(deviceId, pointer); + touchedWindow.addHoveringPointer(deviceId, pointer, x, y); windows.push_back(touchedWindow); } diff --git a/services/inputflinger/dispatcher/TouchState.h b/services/inputflinger/dispatcher/TouchState.h index 9d4bb3d943..3fbe584a64 100644 --- a/services/inputflinger/dispatcher/TouchState.h +++ b/services/inputflinger/dispatcher/TouchState.h @@ -49,7 +49,8 @@ struct TouchState { DeviceId deviceId, const std::vector<PointerProperties>& touchingPointers, std::optional<nsecs_t> firstDownTimeInTarget = std::nullopt); void addHoveringPointerToWindow(const sp<android::gui::WindowInfoHandle>& windowHandle, - DeviceId deviceId, const PointerProperties& pointer); + DeviceId deviceId, const PointerProperties& pointer, float x, + float y); void removeHoveringPointer(DeviceId deviceId, int32_t pointerId); void clearHoveringPointers(DeviceId deviceId); diff --git a/services/inputflinger/dispatcher/TouchedWindow.cpp b/services/inputflinger/dispatcher/TouchedWindow.cpp index 1f86f6635a..fa5be1a719 100644 --- a/services/inputflinger/dispatcher/TouchedWindow.cpp +++ b/services/inputflinger/dispatcher/TouchedWindow.cpp @@ -36,6 +36,13 @@ bool hasPointerId(const std::vector<PointerProperties>& pointers, int32_t pointe }) != pointers.end(); } +bool hasPointerId(const std::vector<TouchedWindow::HoveringPointer>& pointers, int32_t pointerId) { + return std::find_if(pointers.begin(), pointers.end(), + [&pointerId](const TouchedWindow::HoveringPointer& pointer) { + return pointer.properties.id == pointerId; + }) != pointers.end(); +} + } // namespace bool TouchedWindow::hasHoveringPointers() const { @@ -78,16 +85,18 @@ bool TouchedWindow::hasHoveringPointer(DeviceId deviceId, int32_t pointerId) con return hasPointerId(state.hoveringPointers, pointerId); } -void TouchedWindow::addHoveringPointer(DeviceId deviceId, const PointerProperties& pointer) { - std::vector<PointerProperties>& hoveringPointers = mDeviceStates[deviceId].hoveringPointers; +void TouchedWindow::addHoveringPointer(DeviceId deviceId, const PointerProperties& properties, + float x, float y) { + std::vector<HoveringPointer>& hoveringPointers = mDeviceStates[deviceId].hoveringPointers; const size_t initialSize = hoveringPointers.size(); - std::erase_if(hoveringPointers, [&pointer](const PointerProperties& properties) { - return properties.id == pointer.id; + std::erase_if(hoveringPointers, [&properties](const HoveringPointer& pointer) { + return pointer.properties.id == properties.id; }); if (hoveringPointers.size() != initialSize) { - LOG(ERROR) << __func__ << ": " << pointer << ", device " << deviceId << " was in " << *this; + LOG(ERROR) << __func__ << ": " << properties << ", device " << deviceId << " was in " + << *this; } - hoveringPointers.push_back(pointer); + hoveringPointers.push_back({properties, x, y}); } Result<void> TouchedWindow::addTouchingPointers(DeviceId deviceId, @@ -173,8 +182,8 @@ bool TouchedWindow::hasActiveStylus() const { return true; } } - for (const PointerProperties& properties : state.hoveringPointers) { - if (properties.toolType == ToolType::STYLUS) { + for (const HoveringPointer& pointer : state.hoveringPointers) { + if (pointer.properties.toolType == ToolType::STYLUS) { return true; } } @@ -270,8 +279,8 @@ void TouchedWindow::removeHoveringPointer(DeviceId deviceId, int32_t pointerId) } DeviceState& state = stateIt->second; - std::erase_if(state.hoveringPointers, [&pointerId](const PointerProperties& properties) { - return properties.id == pointerId; + std::erase_if(state.hoveringPointers, [&pointerId](const HoveringPointer& pointer) { + return pointer.properties.id == pointerId; }); if (!state.hasPointers()) { @@ -279,6 +288,22 @@ void TouchedWindow::removeHoveringPointer(DeviceId deviceId, int32_t pointerId) } } +std::vector<DeviceId> TouchedWindow::eraseHoveringPointersIf( + std::function<bool(const PointerProperties&, float /*x*/, float /*y*/)> condition) { + std::vector<DeviceId> erasedDevices; + for (auto& [deviceId, state] : mDeviceStates) { + std::erase_if(state.hoveringPointers, [&](const HoveringPointer& pointer) { + if (condition(pointer.properties, pointer.x, pointer.y)) { + erasedDevices.push_back(deviceId); + return true; + } + return false; + }); + } + + return erasedDevices; +} + void TouchedWindow::removeAllHoveringPointersForDevice(DeviceId deviceId) { const auto stateIt = mDeviceStates.find(deviceId); if (stateIt == mDeviceStates.end()) { @@ -312,6 +337,11 @@ std::string TouchedWindow::dump() const { return out; } +std::ostream& operator<<(std::ostream& out, const TouchedWindow::HoveringPointer& pointer) { + out << pointer.properties << " at (" << pointer.x << ", " << pointer.y << ")"; + return out; +} + std::ostream& operator<<(std::ostream& out, const TouchedWindow& window) { out << window.dump(); return out; diff --git a/services/inputflinger/dispatcher/TouchedWindow.h b/services/inputflinger/dispatcher/TouchedWindow.h index 4f0ad1628a..c38681eef0 100644 --- a/services/inputflinger/dispatcher/TouchedWindow.h +++ b/services/inputflinger/dispatcher/TouchedWindow.h @@ -38,7 +38,7 @@ struct TouchedWindow { bool hasHoveringPointers() const; bool hasHoveringPointers(DeviceId deviceId) const; bool hasHoveringPointer(DeviceId deviceId, int32_t pointerId) const; - void addHoveringPointer(DeviceId deviceId, const PointerProperties& pointer); + void addHoveringPointer(DeviceId deviceId, const PointerProperties& pointer, float x, float y); void removeHoveringPointer(DeviceId deviceId, int32_t pointerId); // Touching @@ -69,6 +69,15 @@ struct TouchedWindow { void clearHoveringPointers(DeviceId deviceId); std::string dump() const; + struct HoveringPointer { + PointerProperties properties; + float x; + float y; + }; + + std::vector<DeviceId> eraseHoveringPointersIf( + std::function<bool(const PointerProperties&, float /*x*/, float /*y*/)> condition); + private: struct DeviceState { std::vector<PointerProperties> touchingPointers; @@ -78,7 +87,7 @@ private: // NOTE: This is not initialized in case of HOVER entry/exit and DISPATCH_AS_OUTSIDE // scenario. std::optional<nsecs_t> downTimeInTarget; - std::vector<PointerProperties> hoveringPointers; + std::vector<HoveringPointer> hoveringPointers; bool hasPointers() const { return !touchingPointers.empty() || !hoveringPointers.empty(); }; }; diff --git a/services/inputflinger/tests/InputDispatcher_test.cpp b/services/inputflinger/tests/InputDispatcher_test.cpp index 73ab0dae41..48930ef444 100644 --- a/services/inputflinger/tests/InputDispatcher_test.cpp +++ b/services/inputflinger/tests/InputDispatcher_test.cpp @@ -1923,6 +1923,99 @@ TEST_F(InputDispatcherTest, HoverMoveAndScroll) { window->consumeMotionEvent(WithMotionAction(ACTION_SCROLL)); } +/** + * Two windows: a trusted overlay and a regular window underneath. Both windows are visible. + * Mouse is hovered, and the hover event should only go to the overlay. + * However, next, the touchable region of the trusted overlay shrinks. The mouse position hasn't + * changed, but the cursor would now end up hovering above the regular window underneatch. + * If the mouse is now clicked, this would generate an ACTION_DOWN event, which would go to the + * regular window. However, the trusted overlay is also watching for outside touch. + * The trusted overlay should get two events: + * 1) The ACTION_OUTSIDE event, since the click is now not inside its touchable region + * 2) The HOVER_EXIT event, since the mouse pointer is no longer hovering inside this window + * + * This test reproduces a crash where there is an overlap between dispatch modes for the trusted + * overlay touch target, since the event is causing both an ACTION_OUTSIDE, and as a HOVER_EXIT. + */ +TEST_F(InputDispatcherTest, MouseClickUnderShrinkingTrustedOverlay) { + std::shared_ptr<FakeApplicationHandle> app = std::make_shared<FakeApplicationHandle>(); + sp<FakeWindowHandle> overlay = sp<FakeWindowHandle>::make(app, mDispatcher, "Trusted overlay", + ui::LogicalDisplayId::DEFAULT); + overlay->setTrustedOverlay(true); + overlay->setWatchOutsideTouch(true); + overlay->setFrame(Rect(0, 0, 200, 200)); + + sp<FakeWindowHandle> window = sp<FakeWindowHandle>::make(app, mDispatcher, "Regular window", + ui::LogicalDisplayId::DEFAULT); + window->setFrame(Rect(0, 0, 200, 200)); + + mDispatcher->onWindowInfosChanged({{*overlay->getInfo(), *window->getInfo()}, {}, 0, 0}); + // Hover the mouse into the overlay + mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_HOVER_MOVE, AINPUT_SOURCE_MOUSE) + .pointer(PointerBuilder(0, ToolType::MOUSE).x(100).y(110)) + .build()); + overlay->consumeMotionEvent(WithMotionAction(ACTION_HOVER_ENTER)); + + // Now, shrink the touchable region of the overlay! This will cause the cursor to suddenly have + // the regular window as the touch target + overlay->setTouchableRegion(Region({0, 0, 0, 0})); + mDispatcher->onWindowInfosChanged({{*overlay->getInfo(), *window->getInfo()}, {}, 0, 0}); + + // Now we can click with the mouse. The click should go into the regular window + mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_MOUSE) + .pointer(PointerBuilder(0, ToolType::MOUSE).x(100).y(110)) + .build()); + overlay->consumeMotionEvent(WithMotionAction(ACTION_HOVER_EXIT)); + overlay->consumeMotionEvent(WithMotionAction(ACTION_OUTSIDE)); + window->consumeMotionEvent(WithMotionAction(ACTION_DOWN)); +} + +/** + * Similar to above, but also has a spy on top that also catches the HOVER + * events. Also, instead of ACTION_DOWN, we are continuing to send the hovering + * stream to ensure that the spy receives hover events correctly. + */ +TEST_F(InputDispatcherTest, MouseClickUnderShrinkingTrustedOverlayWithSpy) { + std::shared_ptr<FakeApplicationHandle> app = std::make_shared<FakeApplicationHandle>(); + sp<FakeWindowHandle> spyWindow = + sp<FakeWindowHandle>::make(app, mDispatcher, "Spy", ui::LogicalDisplayId::DEFAULT); + spyWindow->setFrame(Rect(0, 0, 200, 200)); + spyWindow->setTrustedOverlay(true); + spyWindow->setSpy(true); + sp<FakeWindowHandle> overlay = sp<FakeWindowHandle>::make(app, mDispatcher, "Trusted overlay", + ui::LogicalDisplayId::DEFAULT); + overlay->setTrustedOverlay(true); + overlay->setWatchOutsideTouch(true); + overlay->setFrame(Rect(0, 0, 200, 200)); + + sp<FakeWindowHandle> window = sp<FakeWindowHandle>::make(app, mDispatcher, "Regular window", + ui::LogicalDisplayId::DEFAULT); + window->setFrame(Rect(0, 0, 200, 200)); + + mDispatcher->onWindowInfosChanged( + {{*spyWindow->getInfo(), *overlay->getInfo(), *window->getInfo()}, {}, 0, 0}); + // Hover the mouse into the overlay + mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_HOVER_MOVE, AINPUT_SOURCE_MOUSE) + .pointer(PointerBuilder(0, ToolType::MOUSE).x(100).y(110)) + .build()); + spyWindow->consumeMotionEvent(WithMotionAction(ACTION_HOVER_ENTER)); + overlay->consumeMotionEvent(WithMotionAction(ACTION_HOVER_ENTER)); + + // Now, shrink the touchable region of the overlay! This will cause the cursor to suddenly have + // the regular window as the touch target + overlay->setTouchableRegion(Region({0, 0, 0, 0})); + mDispatcher->onWindowInfosChanged( + {{*spyWindow->getInfo(), *overlay->getInfo(), *window->getInfo()}, {}, 0, 0}); + + // Now we can click with the mouse. The click should go into the regular window + mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_HOVER_MOVE, AINPUT_SOURCE_MOUSE) + .pointer(PointerBuilder(0, ToolType::MOUSE).x(110).y(110)) + .build()); + spyWindow->consumeMotionEvent(WithMotionAction(ACTION_HOVER_MOVE)); + overlay->consumeMotionEvent(WithMotionAction(ACTION_HOVER_EXIT)); + window->consumeMotionEvent(WithMotionAction(ACTION_HOVER_ENTER)); +} + using InputDispatcherMultiDeviceTest = InputDispatcherTest; /** |